/* This file was generated from TypeScript source tslib/webgl/graphicsdevice.ts */

// Copyright (c) 2011-2013 Turbulenz Limited
/*global TurbulenzEngine*/
/*global TGALoader*/
/*global DDSLoader*/
/*global TARLoader*/
/*global Int8Array*/
/*global Int16Array*/
/*global Int32Array*/
/*global Uint8Array*/
/*global Uint8ClampedArray*/
/*global Uint16Array*/
/*global Uint32Array*/
/*global Float32Array*/
/*global ArrayBuffer*/
/*global DataView*/
/*global window*/
/*global debug*/
"use strict";



// -----------------------------------------------------------------------------
function TZWebGLTexture() {
    return this;
}
TZWebGLTexture.prototype = {
    version: 1,
    setData: function textureSetDataFn(data) {
        var gd = this.gd;
        var target = this.target;
        gd.bindTexture(target, this.glTexture);
        this.updateData(data);
        gd.bindTexture(target, null);
    },
    createGLTexture: // Internal
    function createGLTextureFn(data) {
        var gd = this.gd;
        var gl = gd.gl;
        var target;
        if(this.cubemap) {
            target = gl.TEXTURE_CUBE_MAP;
        } else if(this.depth > 1) {
            //target = gl.TEXTURE_3D;
            // 3D textures are not supported yet
            return false;
        } else {
            target = gl.TEXTURE_2D;
        }
        this.target = target;
        var gltex = gl.createTexture();
        this.glTexture = gltex;
        gd.bindTexture(target, gltex);
        gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        if(this.mipmaps || 1 < this.numDataLevels) {
            gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
        } else {
            gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        }
        gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        this.updateData(data);
        gd.bindTexture(target, null);
        return true;
    },
    updateData: function updateDataFn(data) {
        var gd = this.gd;
        var gl = gd.gl;
        function log2(a) {
            return Math.floor(Math.log(a) / Math.LN2);
        }
        var generateMipMaps = this.mipmaps && (this.numDataLevels !== (1 + Math.max(log2(this.width), log2(this.height))));
        var format = this.format;
        var internalFormat, gltype, srcStep, bufferData = null;
        var compressedTexturesExtension;
        if(format === gd.PIXELFORMAT_A8) {
            internalFormat = gl.ALPHA;
            gltype = gl.UNSIGNED_BYTE;
            srcStep = 1;
            if(data && !data.src) {
                if(data instanceof Uint8Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint8Array(data);
                }
            }
        } else if(format === gd.PIXELFORMAT_L8) {
            internalFormat = gl.LUMINANCE;
            gltype = gl.UNSIGNED_BYTE;
            srcStep = 1;
            if(data && !data.src) {
                if(data instanceof Uint8Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint8Array(data);
                }
            }
        } else if(format === gd.PIXELFORMAT_L8A8) {
            internalFormat = gl.LUMINANCE_ALPHA;
            gltype = gl.UNSIGNED_BYTE;
            srcStep = 2;
            if(data && !data.src) {
                if(data instanceof Uint8Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint8Array(data);
                }
            }
        } else if(format === gd.PIXELFORMAT_R5G5B5A1) {
            internalFormat = gl.RGBA;
            gltype = gl.UNSIGNED_SHORT_5_5_5_1;
            srcStep = 1;
            if(data && !data.src) {
                if(data instanceof Uint16Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint16Array(data);
                }
            }
        } else if(format === gd.PIXELFORMAT_R5G6B5) {
            internalFormat = gl.RGB;
            gltype = gl.UNSIGNED_SHORT_5_6_5;
            srcStep = 1;
            if(data && !data.src) {
                if(data instanceof Uint16Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint16Array(data);
                }
            }
        } else if(format === gd.PIXELFORMAT_R4G4B4A4) {
            internalFormat = gl.RGBA;
            gltype = gl.UNSIGNED_SHORT_4_4_4_4;
            srcStep = 1;
            if(data && !data.src) {
                if(data instanceof Uint16Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint16Array(data);
                }
            }
        } else if(format === gd.PIXELFORMAT_R8G8B8A8) {
            internalFormat = gl.RGBA;
            gltype = gl.UNSIGNED_BYTE;
            srcStep = 4;
            if(data && !data.src) {
                if(data instanceof Uint8Array) {
                    // Some browsers consider Uint8ClampedArray to be
                    // an instance of Uint8Array (which is correct as
                    // per the spec), yet won't accept a
                    // Uint8ClampedArray as pixel data for a
                    // gl.UNSIGNED_BYTE Texture.  If we have a
                    // Uint8ClampedArray then we can just reuse the
                    // underlying data.
                    if(typeof Uint8ClampedArray !== "undefined" && data instanceof Uint8ClampedArray) {
                        bufferData = new Uint8Array(data.buffer);
                    } else {
                        bufferData = data;
                    }
                } else {
                    bufferData = new Uint8Array(data);
                }
            }
        } else if(format === gd.PIXELFORMAT_R8G8B8) {
            internalFormat = gl.RGB;
            gltype = gl.UNSIGNED_BYTE;
            srcStep = 3;
            if(data && !data.src) {
                if(data instanceof Uint8Array) {
                    // See comment above about Uint8ClampedArray
                    if(typeof Uint8ClampedArray !== "undefined" && data instanceof Uint8ClampedArray) {
                        bufferData = new Uint8Array(data.buffer);
                    } else {
                        bufferData = data;
                    }
                } else {
                    bufferData = new Uint8Array(data);
                }
            }
        } else if(format === gd.PIXELFORMAT_D24S8) {
            //internalFormat = gl.DEPTH24_STENCIL8_EXT;
            //gltype = gl.UNSIGNED_INT_24_8_EXT;
            //internalFormat = gl.DEPTH_COMPONENT;
            internalFormat = gl.DEPTH_STENCIL;
            gltype = gl.UNSIGNED_INT;
            srcStep = 1;
            if(data && !data.src) {
                bufferData = new Uint32Array(data);
            }
        } else if(format === gd.PIXELFORMAT_DXT1 || format === gd.PIXELFORMAT_DXT3 || format === gd.PIXELFORMAT_DXT5) {
            compressedTexturesExtension = gd.compressedTexturesExtension;
            if(compressedTexturesExtension) {
                if(format === gd.PIXELFORMAT_DXT1) {
                    internalFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
                    srcStep = 8;
                } else if(format === gd.PIXELFORMAT_DXT3) {
                    internalFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
                    srcStep = 16;
                } else//if (format === gd.PIXELFORMAT_DXT5)
                 {
                    internalFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
                    srcStep = 16;
                }
                if(internalFormat === undefined) {
                    return;// Unsupported format
                    
                }
                if(data && !data.src) {
                    if(data instanceof Uint8Array) {
                        bufferData = data;
                    } else {
                        bufferData = new Uint8Array(data);
                    }
                }
            } else {
                return;// Unsupported format
                
            }
        } else {
            return;//unknown/unsupported format
            
        }
        var numLevels = (data && 0 < this.numDataLevels ? this.numDataLevels : 1);
        var w = this.width, h = this.height, offset = 0, target, n, levelSize, levelData;
        if(this.cubemap) {
            if(data && data instanceof WebGLVideo) {
                return;//unknown/unsupported format
                
            }
            target = gl.TEXTURE_CUBE_MAP;
            for(var f = 0; f < 6; f += 1) {
                var faceTarget = (gl.TEXTURE_CUBE_MAP_POSITIVE_X + f);
                for(n = 0; n < numLevels; n += 1) {
                    if(compressedTexturesExtension) {
                        levelSize = (Math.floor((w + 3) / 4) * Math.floor((h + 3) / 4) * srcStep);
                        if(bufferData) {
                            if(numLevels === 1) {
                                levelData = bufferData;
                            } else {
                                levelData = bufferData.subarray(offset, (offset + levelSize));
                            }
                        } else {
                            levelData = new Uint8Array(levelSize);
                        }
                        if(gd.WEBGL_compressed_texture_s3tc) {
                            gl.compressedTexImage2D(faceTarget, n, internalFormat, w, h, 0, levelData);
                        } else {
                            compressedTexturesExtension.compressedTexImage2D(faceTarget, n, internalFormat, w, h, 0, levelData);
                        }
                    } else {
                        levelSize = (w * h * srcStep);
                        if(bufferData) {
                            if(numLevels === 1) {
                                levelData = bufferData;
                            } else {
                                levelData = bufferData.subarray(offset, (offset + levelSize));
                            }
                            gl.texImage2D(faceTarget, n, internalFormat, w, h, 0, internalFormat, gltype, levelData);
                        } else if(data) {
                            gl.texImage2D(faceTarget, n, internalFormat, internalFormat, gltype, data);
                        } else {
                            if(gltype === gl.UNSIGNED_SHORT_5_6_5 || gltype === gl.UNSIGNED_SHORT_5_5_5_1 || gltype === gl.UNSIGNED_SHORT_4_4_4_4) {
                                levelData = new Uint16Array(levelSize);
                            } else {
                                levelData = new Uint8Array(levelSize);
                            }
                            gl.texImage2D(faceTarget, n, internalFormat, w, h, 0, internalFormat, gltype, levelData);
                        }
                    }
                    offset += levelSize;
                    w = (w > 1 ? Math.floor(w / 2) : 1);
                    h = (h > 1 ? Math.floor(h / 2) : 1);
                }
                w = this.width;
                h = this.height;
            }
        } else if(data && data instanceof WebGLVideo) {
            target = gl.TEXTURE_2D;
            gl.texImage2D(target, 0, internalFormat, internalFormat, gltype, data.video);
        } else {
            target = gl.TEXTURE_2D;
            for(n = 0; n < numLevels; n += 1) {
                if(compressedTexturesExtension) {
                    levelSize = (Math.floor((w + 3) / 4) * Math.floor((h + 3) / 4) * srcStep);
                    if(bufferData) {
                        if(numLevels === 1) {
                            levelData = bufferData;
                        } else {
                            levelData = bufferData.subarray(offset, (offset + levelSize));
                        }
                    } else {
                        levelData = new Uint8Array(levelSize);
                    }
                    if(gd.WEBGL_compressed_texture_s3tc) {
                        gl.compressedTexImage2D(target, n, internalFormat, w, h, 0, levelData);
                    } else {
                        compressedTexturesExtension.compressedTexImage2D(target, n, internalFormat, w, h, 0, levelData);
                    }
                } else {
                    levelSize = (w * h * srcStep);
                    if(bufferData) {
                        if(numLevels === 1) {
                            levelData = bufferData;
                        } else {
                            levelData = bufferData.subarray(offset, (offset + levelSize));
                        }
                        gl.texImage2D(target, n, internalFormat, w, h, 0, internalFormat, gltype, levelData);
                    } else if(data) {
                        gl.texImage2D(target, n, internalFormat, internalFormat, gltype, data);
                    } else {
                        if(gltype === gl.UNSIGNED_SHORT_5_6_5 || gltype === gl.UNSIGNED_SHORT_5_5_5_1 || gltype === gl.UNSIGNED_SHORT_4_4_4_4) {
                            levelData = new Uint16Array(levelSize);
                        } else {
                            levelData = new Uint8Array(levelSize);
                        }
                        gl.texImage2D(target, n, internalFormat, w, h, 0, internalFormat, gltype, levelData);
                    }
                }
                offset += levelSize;
                w = (w > 1 ? Math.floor(w / 2) : 1);
                h = (h > 1 ? Math.floor(h / 2) : 1);
            }
        }
        if(generateMipMaps) {
            gl.generateMipmap(target);
        }
    },
    updateMipmaps: function updateMipmapsFn(face) {
        if(this.mipmaps) {
            if(this.depth > 1) {
                (TurbulenzEngine).callOnError("3D texture mipmap generation unsupported");
                return;
            }
            if(this.cubemap && face !== 5) {
                return;
            }
            var gd = this.gd;
            var gl = gd.gl;
            var target = this.target;
            gd.bindTexture(target, this.glTexture);
            gl.generateMipmap(target);
            gd.bindTexture(target, null);
        }
    },
    destroy: function textureDestroyFn() {
        var gd = this.gd;
        if(gd) {
            var glTexture = this.glTexture;
            if(glTexture) {
                var gl = gd.gl;
                if(gl) {
                    gd.unbindTexture(glTexture);
                    gl.deleteTexture(glTexture);
                }
                delete this.glTexture;
            }
            delete this.sampler;
            delete this.gd;
        }
    },
    typedArrayIsValid: function textureTypedArrayIsValidFn(typedArray) {
        var gd = this.gd;
        var format = this.format;
        if(gd) {
            if((format === gd.PIXELFORMAT_A8) || (format === gd.PIXELFORMAT_L8) || (format === gd.PIXELFORMAT_S8)) {
                return ((typedArray instanceof Uint8Array) || (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray)) && (typedArray.length === this.width * this.height * this.depth);
            }
            if(format === gd.PIXELFORMAT_L8A8) {
                return ((typedArray instanceof Uint8Array) || (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray)) && (typedArray.length === 2 * this.width * this.height * this.depth);
            }
            if(format === gd.PIXELFORMAT_R8G8B8) {
                return ((typedArray instanceof Uint8Array) || (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray)) && (typedArray.length === 3 * this.width * this.height * this.depth);
            }
            if(format === gd.PIXELFORMAT_R8G8B8A8) {
                return ((typedArray instanceof Uint8Array) || (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray)) && (typedArray.length === 4 * this.width * this.height * this.depth);
            }
            if((format === gd.PIXELFORMAT_R5G5B5A1) || (format === gd.PIXELFORMAT_R5G6B5) || (format === gd.PIXELFORMAT_R4G4B4A4)) {
                return (typedArray instanceof Uint16Array) && (typedArray.length === this.width * this.height * this.depth);
            }
        }
        return false;
    }
};
// Constructor function
TZWebGLTexture.create = function webGLTextureCreateFn(gd, params) {
    var tex = new TZWebGLTexture();
    tex.gd = gd;
    tex.mipmaps = params.mipmaps;
    tex.dynamic = params.dynamic;
    tex.renderable = params.renderable;
    tex.numDataLevels = 0;
    var src = params.src;
    if(src) {
        tex.name = params.name || src;
        var extension;
        var data = params.data;
        if(data) {
            // do not trust file extensions if we got data...
            if(data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) {
                extension = '.png';
            } else if(data[0] === 255 && data[1] === 216 && data[2] === 255 && (data[3] === 224 || data[3] === 225)) {
                extension = '.jpg';
            } else if(data[0] === 68 && data[1] === 68 && data[2] === 83 && data[3] === 32) {
                extension = '.dds';
            } else {
                extension = src.slice(-4);
            }
        } else {
            extension = src.slice(-4);
        }
        // DDS and TGA textures require out own image loaders
        if(extension === '.dds' || extension === '.tga') {
            if(extension === '.tga' && typeof TGALoader !== 'undefined') {
                var tgaParams = {
                    gd: gd,
                    onload: function tgaLoadedFn(data, width, height, format, status) {
                        tex.width = width;
                        tex.height = height;
                        tex.depth = 1;
                        tex.format = format;
                        tex.cubemap = false;
                        var result = tex.createGLTexture(data);
                        if(params.onload) {
                            params.onload(result ? tex : null, status);
                        }
                    },
                    onerror: function tgaFailedFn() {
                        tex.failed = true;
                        if(params.onload) {
                            params.onload(null);
                        }
                    },
                    data: undefined,
                    src: undefined
                };
                if(data) {
                    tgaParams.data = data;
                } else {
                    tgaParams.src = src;
                }
                TGALoader.create(tgaParams);
                return tex;
            } else if(extension === '.dds' && typeof DDSLoader !== 'undefined') {
                var ddsParams = {
                    gd: gd,
                    onload: function ddsLoadedFn(data, width, height, format, numLevels, cubemap, depth, status) {
                        tex.width = width;
                        tex.height = height;
                        tex.format = format;
                        tex.cubemap = cubemap;
                        tex.depth = depth;
                        tex.numDataLevels = numLevels;
                        var result = tex.createGLTexture(data);
                        if(params.onload) {
                            params.onload(result ? tex : null, status);
                        }
                    },
                    onerror: function ddsFailedFn() {
                        tex.failed = true;
                        if(params.onload) {
                            params.onload(null);
                        }
                    },
                    data: undefined,
                    src: undefined
                };
                if(data) {
                    ddsParams.data = data;
                } else {
                    ddsParams.src = src;
                }
                DDSLoader.create(ddsParams);
                return tex;
            } else {
                (TurbulenzEngine).callOnError('Missing image loader required for ' + src);
                tex = TZWebGLTexture.create(gd, {
                    name: (params.name || src),
                    width: 2,
                    height: 2,
                    depth: 1,
                    format: 'R8G8B8A8',
                    cubemap: false,
                    mipmaps: params.mipmaps,
                    dynamic: params.dynamic,
                    renderable: params.renderable,
                    data: [
                        255, 
                        20, 
                        147, 
                        255, 
                        255, 
                        0, 
                        0, 
                        255, 
                        255, 
                        255, 
                        255, 
                        255, 
                        255, 
                        20, 
                        147, 
                        255
                    ]
                });
                if(params.onload) {
                    if(TurbulenzEngine) {
                        TurbulenzEngine.setTimeout(function () {
                            params.onload(tex, 200);
                        }, 0);
                    } else {
                        window.setTimeout(function () {
                            params.onload(tex, 200);
                        }, 0);
                    }
                }
                return tex;
            }
        }
        var img = new Image();
        img.onload = function imageLoadedFn() {
            tex.width = img.width;
            tex.height = img.height;
            tex.depth = 1;
            tex.format = gd.PIXELFORMAT_R8G8B8A8;
            tex.cubemap = false;
            var result = tex.createGLTexture(img);
            if(params.onload) {
                params.onload(result ? tex : null, 200);
            }
        };
        img.onerror = function imageFailedFn() {
            tex.failed = true;
            if(params.onload) {
                params.onload(null);
            }
        };
        if(data) {
            if(extension === '.jpg' || extension === '.jpeg') {
                src = 'data:image/jpeg;base64,' + (TurbulenzEngine).base64Encode(data);
            } else if(extension === '.png') {
                src = 'data:image/png;base64,' + (TurbulenzEngine).base64Encode(data);
            }
        } else {
            img.crossOrigin = 'anonymous';
        }
        img.src = src;
    } else {
        // Invalid src values like "" fall through to here
        if("" === src && params.onload) {
            // Assume the caller intended to pass in a valid url.
            return null;
        }
        var format = params.format;
        if(typeof format === 'string') {
            format = gd['PIXELFORMAT_' + format];
        }
        tex.width = params.width;
        tex.height = params.height;
        tex.depth = params.depth;
        tex.format = format;
        tex.cubemap = params.cubemap;
        tex.name = params.name;
        var result = tex.createGLTexture(params.data);
        if(!result) {
            tex = null;
        }
        // If this is a depth-texture, note the attachment type
        // required, based on the format.
        if(params.renderable) {
            if(gd.PIXELFORMAT_D16 === format) {
                tex.glDepthAttachment = gd.gl.DEPTH_ATTACHMENT;
            } else if(gd.PIXELFORMAT_D24S8 === format) {
                tex.glDepthAttachment = gd.gl.DEPTH_STENCIL_ATTACHMENT;
            }
        }
        if(params.onload) {
            params.onload(tex, 200);
        }
    }
    return tex;
};
//
// WebGLVideo
//
function WebGLVideo() {
    return this;
}
WebGLVideo.prototype = {
    version: 1,
    play: // Public API
    function videoPlayFn(seek) {
        var video = this.video;
        if(!this.playing) {
            this.playing = true;
            this.paused = false;
        }
        if(seek === undefined) {
            seek = 0;
        }
        if(0.01 < Math.abs(video.currentTime - seek)) {
            try  {
                video.currentTime = seek;
            } catch (e) {
                // There does not seem to be any reliable way of seeking
                            }
        }
        video.play();
        return true;
    },
    stop: function videoStopFn() {
        var playing = this.playing;
        if(playing) {
            this.playing = false;
            this.paused = false;
            var video = this.video;
            video.pause();
            video.currentTime = 0;
        }
        return playing;
    },
    pause: function videoPauseFn() {
        if(this.playing) {
            if(!this.paused) {
                this.paused = true;
                this.video.pause();
            }
            return true;
        }
        return false;
    },
    resume: function videoResumeFn(seek) {
        if(this.paused) {
            this.paused = false;
            var video = this.video;
            if(seek !== undefined) {
                if(0.01 < Math.abs(video.currentTime - seek)) {
                    try  {
                        video.currentTime = seek;
                    } catch (e) {
                        // There does not seem to be any reliable way of seeking
                                            }
                }
            }
            video.play();
            return true;
        }
        return false;
    },
    rewind: function videoRewindFn() {
        if(this.playing) {
            this.video.currentTime = 0;
            return true;
        }
        return false;
    },
    destroy: function videoDestroyFn() {
        this.stop();
        if(this.video) {
            if(this.elementAdded) {
                this.elementAdded = false;
                TurbulenzEngine.canvas.parentElement.removeChild(this.video);
            }
            this.video = null;
        }
    }
};
WebGLVideo.create = function webGLSoundSourceCreateFn(params) {
    var v = new WebGLVideo();
    var onload = params.onload;
    var looping = params.looping;
    var src = params.src;
    var userAgent = navigator.userAgent.toLowerCase();
    var video = (document.createElement('video'));
    video.preload = 'auto';
    video.autobuffer = true;
    video.muted = true;
    if(looping) {
        if(video.loop !== undefined && !userAgent.match(/firefox/)) {
            video.loop = true;
        } else {
            video.onended = function () {
                video.src = src;
                video.play();
            };
        }
    } else {
        video.onended = function () {
            v.playing = false;
        };
    }
    v.video = video;
    v.src = src;
    v.playing = false;
    v.paused = false;
    // Safari does not play the video unless is on the page...
    if(userAgent.match(/safari/) && !userAgent.match(/chrome/)) {
        //video.setAttribute("style", "display: none;");
        video.setAttribute("style", "visibility: hidden;");
        TurbulenzEngine.canvas.parentElement.appendChild(video);
        v.elementAdded = true;
    }
    if(video.webkitDecodedFrameCount !== undefined) {
        var lastFrameCount = -1, tell = 0;
        Object.defineProperty(v, "tell", {
            get: function tellFn() {
                if(lastFrameCount !== this.video.webkitDecodedFrameCount) {
                    lastFrameCount = this.video.webkitDecodedFrameCount;
                    tell = this.video.currentTime;
                }
                return tell;
            },
            enumerable: true,
            configurable: false
        });
    } else {
        Object.defineProperty(v, "tell", {
            get: function tellFn() {
                return this.video.currentTime;
            },
            enumerable: true,
            configurable: false
        });
    }
    Object.defineProperty(v, "looping", {
        get: function loopingFn() {
            return looping;
        },
        enumerable: true,
        configurable: false
    });
    var loadingVideoFailed = function loadingVideoFailedFn() {
        /* e */ if(onload) {
            onload(null);
            onload = null;
        }
        video.removeEventListener("error", loadingVideoFailed);
        video = null;
        v.video = null;
        v.playing = false;
    };
    video.addEventListener("error", loadingVideoFailed, false);
    var videoCanPlay = function videoCanPlayFn() {
        v.length = video.duration;
        v.width = video.videoWidth;
        v.height = video.videoHeight;
        if(onload) {
            onload(v, 200);
            onload = null;
        }
        video.removeEventListener("progress", checkProgress);
        video.removeEventListener("canplaythrough", videoCanPlay);
    };
    var checkProgress = function checkProgressFn() {
        if(0 < video.buffered.length && video.buffered.end(0) >= video.duration) {
            videoCanPlay();
        }
    };
    video.addEventListener("progress", checkProgress, false);
    video.addEventListener("canplaythrough", videoCanPlay, false);
    video.crossorigin = 'anonymous';
    video.src = src;
    return v;
};

function WebGLRenderBuffer() {
    return this;
}
WebGLRenderBuffer.prototype = {
    version: 1,
    destroy: function renderBufferDestroyFn() {
        var gd = this.gd;
        if(gd) {
            var glBuffer = this.glBuffer;
            if(glBuffer) {
                var gl = gd.gl;
                if(gl) {
                    gl.deleteRenderbuffer(glBuffer);
                }
                delete this.glBuffer;
            }
            delete this.gd;
        }
    }
};
// Constructor function
WebGLRenderBuffer.create = function webGLRenderBufferFn(gd, params) {
    var renderBuffer = new WebGLRenderBuffer();
    var width = params.width;
    var height = params.height;
    var format = params.format;
    if(typeof format === 'string') {
        format = gd['PIXELFORMAT_' + format];
    }
    if(format !== gd.PIXELFORMAT_D24S8 && format !== gd.PIXELFORMAT_D16) {
        return null;
    }
    var gl = gd.gl;
    var glBuffer = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, glBuffer);
    var internalFormat;
    var attachment;
    if(gd.PIXELFORMAT_D16 === format) {
        internalFormat = gl.DEPTH_COMPONENT16;
        attachment = gl.DEPTH_ATTACHMENT;
    } else//if (gd.PIXELFORMAT_D24S8 === format)
     {
        internalFormat = gl.DEPTH_STENCIL;
        attachment = gl.DEPTH_STENCIL_ATTACHMENT;
    }
    // else if (gd.PIXELFORMAT_S8 === format)
    // {
    //     internalFormat = gl.STENCIL_INDEX8;
    // }
    gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, width, height);
    renderBuffer.width = gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_WIDTH);
    renderBuffer.height = gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_HEIGHT);
    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
    if(renderBuffer.width < width || renderBuffer.height < height) {
        gl.deleteRenderbuffer(glBuffer);
        return null;
    }
    renderBuffer.gd = gd;
    renderBuffer.format = format;
    renderBuffer.glDepthAttachment = attachment;
    renderBuffer.glBuffer = glBuffer;
    return renderBuffer;
};

function WebGLRenderTarget() {
    return this;
}
WebGLRenderTarget.prototype = {
    version: 1,
    oldViewportBox: // Shared because there can only be one active at a time
    [],
    oldScissorBox: [],
    copyBox: function copyBoxFn(dst, src) {
        dst[0] = src[0];
        dst[1] = src[1];
        dst[2] = src[2];
        dst[3] = src[3];
    },
    bind: function bindFn() {
        var gd = this.gd;
        var gl = gd.gl;
        gd.unbindTexture(this.colorTexture0.glTexture);
        if(this.depthTexture) {
            gd.unbindTexture(this.depthTexture.glTexture);
        }
        gl.bindFramebuffer(gl.FRAMEBUFFER, this.glObject);
        var state = gd.state;
        this.copyBox(this.oldViewportBox, state.viewportBox);
        this.copyBox(this.oldScissorBox, state.scissorBox);
        gd.setViewport(0, 0, this.width, this.height);
        gd.setScissor(0, 0, this.width, this.height);
        return true;
    },
    unbind: function unbindFn() {
        var gd = this.gd;
        var gl = gd.gl;
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gd.setViewport.apply(gd, this.oldViewportBox);
        gd.setScissor.apply(gd, this.oldScissorBox);
        this.colorTexture0.updateMipmaps(this.face);
        if(this.depthTexture) {
            this.depthTexture.updateMipmaps(this.face);
        }
    },
    destroy: function renderTargetDestroyFn() {
        var gd = this.gd;
        if(gd) {
            var glObject = this.glObject;
            if(glObject) {
                var gl = gd.gl;
                if(gl) {
                    gl.deleteFramebuffer(glObject);
                }
                delete this.glObject;
            }
            delete this.colorTexture0;
            delete this.colorTexture1;
            delete this.colorTexture2;
            delete this.colorTexture3;
            delete this.depthBuffer;
            delete this.depthTexture;
            delete this.gd;
        }
    }
};
// Constructor function
WebGLRenderTarget.create = function webGLRenderTargetFn(gd, params) {
    var renderTarget = new WebGLRenderTarget();
    var colorTexture0 = params.colorTexture0;
    var colorTexture1 = (colorTexture0 ? (params.colorTexture1 || null) : null);
    var colorTexture2 = (colorTexture1 ? (params.colorTexture2 || null) : null);
    var colorTexture3 = (colorTexture2 ? (params.colorTexture3 || null) : null);
    var depthBuffer = params.depthBuffer || null;
    var depthTexture = params.depthTexture || null;
    var face = params.face;
    var maxSupported = gd.maxSupported("RENDERTARGET_COLOR_TEXTURES");
    if(colorTexture1 && maxSupported < 2) {
        return null;
    }
    if(colorTexture2 && maxSupported < 3) {
        return null;
    }
    if(colorTexture3 && maxSupported < 4) {
        return null;
    }
    var gl = gd.gl;
    var glObject = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, glObject);
    var width, height;
    if(colorTexture0) {
        width = colorTexture0.width;
        height = colorTexture0.height;
        var glTexture = colorTexture0.glTexture;
        if(glTexture === undefined) {
            (TurbulenzEngine).callOnError("Color texture is not a Texture");
            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
            gl.deleteFramebuffer(glObject);
            return null;
        }
        var colorAttachment0 = gl.COLOR_ATTACHMENT0;
        if(colorTexture0.cubemap) {
            gl.framebufferTexture2D(gl.FRAMEBUFFER, colorAttachment0, (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0);
        } else {
            gl.framebufferTexture2D(gl.FRAMEBUFFER, colorAttachment0, gl.TEXTURE_2D, glTexture, 0);
        }
        if(colorTexture1) {
            glTexture = colorTexture1.glTexture;
            if(colorTexture1.cubemap) {
                gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 1), (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0);
            } else {
                gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 1), gl.TEXTURE_2D, glTexture, 0);
            }
            if(colorTexture2) {
                glTexture = colorTexture2.glTexture;
                if(colorTexture1.cubemap) {
                    gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 2), (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0);
                } else {
                    gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 2), gl.TEXTURE_2D, glTexture, 0);
                }
                if(colorTexture3) {
                    glTexture = colorTexture3.glTexture;
                    if(colorTexture1.cubemap) {
                        gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 3), (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0);
                    } else {
                        gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 3), gl.TEXTURE_2D, glTexture, 0);
                    }
                }
            }
        }
    } else if(depthTexture) {
        width = depthTexture.width;
        height = depthTexture.height;
    } else if(depthBuffer) {
        width = depthBuffer.width;
        height = depthBuffer.height;
    } else {
        (TurbulenzEngine).callOnError("No RenderBuffers or Textures specified for this RenderTarget");
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.deleteFramebuffer(glObject);
        return null;
    }
    if(depthTexture) {
        gl.framebufferTexture2D(gl.FRAMEBUFFER, depthTexture.glDepthAttachment, gl.TEXTURE_2D, depthTexture.glTexture, 0);
    } else if(depthBuffer) {
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, depthBuffer.glDepthAttachment, gl.RENDERBUFFER, depthBuffer.glBuffer);
    }
    var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    if(status !== gl.FRAMEBUFFER_COMPLETE) {
        gl.deleteFramebuffer(glObject);
        return null;
    }
    renderTarget.gd = gd;
    renderTarget.glObject = glObject;
    renderTarget.colorTexture0 = colorTexture0;
    renderTarget.colorTexture1 = colorTexture1;
    renderTarget.colorTexture2 = colorTexture2;
    renderTarget.colorTexture3 = colorTexture3;
    renderTarget.depthBuffer = depthBuffer;
    renderTarget.depthTexture = depthTexture;
    renderTarget.width = width;
    renderTarget.height = height;
    renderTarget.face = face;
    return renderTarget;
};


function WebGLIndexBuffer() {
    return this;
}
WebGLIndexBuffer.prototype = {
    version: 1,
    map: function indexBufferMapFn(offset, numIndices) {
        if(offset === undefined) {
            offset = 0;
        }
        if(numIndices === undefined) {
            numIndices = this.numIndices;
        }
        var gd = this.gd;
        var gl = gd.gl;
        var format = this.format;
        var data;
        if(format === gl.UNSIGNED_BYTE) {
            data = new Uint8Array(numIndices);
        } else if(format === gl.UNSIGNED_SHORT) {
            data = new Uint16Array(numIndices);
        } else//if (format === gl.UNSIGNED_INT)
         {
            data = new Uint32Array(numIndices);
        }
        var numValues = 0;
        var writer = function indexBufferWriterFn() {
            var numArguments = arguments.length;
            for(var n = 0; n < numArguments; n += 1) {
                data[numValues] = arguments[n];
                numValues += 1;
            }
        };
        writer.write = writer;
        writer.data = data;
        writer.offset = offset;
        writer.getNumWrittenIndices = function getNumWrittenIndicesFn() {
            return numValues;
        };
        writer.write = writer;
        return writer;
    },
    unmap: function indexBufferUnmapFn(writer) {
        if(writer) {
            var gd = this.gd;
            var gl = gd.gl;
            var data = writer.data;
            delete writer.data;
            var offset = writer.offset;
            delete writer.write;
            var numIndices = writer.getNumWrittenIndices();
            if(!numIndices) {
                return;
            }
            if(numIndices < data.length) {
                data = data.subarray(0, numIndices);
            }
            gd.setIndexBuffer(this);
            if(numIndices < this.numIndices) {
                gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, offset, data);
            } else {
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, this.usage);
            }
        }
    },
    setData: function indexBufferSetDataFn(data, offset, numIndices) {
        if(offset === undefined) {
            offset = 0;
        }
        if(numIndices === undefined) {
            numIndices = this.numIndices;
        }
        var gd = this.gd;
        var gl = gd.gl;
        var bufferData;
        var format = this.format;
        if(format === gl.UNSIGNED_BYTE) {
            if(data instanceof Uint8Array) {
                bufferData = data;
            } else {
                bufferData = new Uint8Array(data);
            }
        } else if(format === gl.UNSIGNED_SHORT) {
            if(data instanceof Uint16Array) {
                bufferData = data;
            } else {
                bufferData = new Uint16Array(data);
            }
            offset *= 2;
        } else if(format === gl.UNSIGNED_INT) {
            if(data instanceof Uint32Array) {
                bufferData = data;
            } else {
                bufferData = new Uint32Array(data);
            }
            offset *= 4;
        }
        data = undefined;
        if(numIndices < bufferData.length) {
            bufferData = bufferData.subarray(0, numIndices);
        }
        gd.setIndexBuffer(this);
        if(numIndices < this.numIndices) {
            gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, offset, bufferData);
        } else {
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, bufferData, this.usage);
        }
    },
    destroy: function indexBufferDestroyFn() {
        var gd = this.gd;
        if(gd) {
            var glBuffer = this.glBuffer;
            if(glBuffer) {
                var gl = gd.gl;
                if(gl) {
                    gd.unsetIndexBuffer(this);
                    gl.deleteBuffer(glBuffer);
                }
                delete this.glBuffer;
            }
            delete this.gd;
        }
    }
};
// Constructor function
WebGLIndexBuffer.create = function webGLIndexBufferCreateFn(gd, params) {
    var gl = gd.gl;
    var ib = new WebGLIndexBuffer();
    ib.gd = gd;
    var numIndices = params.numIndices;
    ib.numIndices = numIndices;
    var format = params.format;
    if(typeof format === "string") {
        format = gd['INDEXFORMAT_' + format];
    }
    ib.format = format;
    var stride;
    if(format === gl.UNSIGNED_BYTE) {
        stride = 1;
    } else if(format === gl.UNSIGNED_SHORT) {
        stride = 2;
    } else//if (format === gl.UNSIGNED_INT)
     {
        stride = 4;
    }
    ib.stride = stride;
    /*jshint sub: true*/
    // Avoid dot notation lookup to prevent Google Closure complaining about transient being a keyword
    ib['transient'] = (params['transient'] || false);
    ib.dynamic = (params.dynamic || ib['transient']);
    ib.usage = (ib['transient'] ? gl.STREAM_DRAW : (ib.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW));
    /*jshint sub: false*/
    ib.glBuffer = gl.createBuffer();
    if(params.data) {
        ib.setData(params.data, 0, numIndices);
    } else {
        gd.setIndexBuffer(ib);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, (numIndices * stride), ib.usage);
    }
    return ib;
};

// TODO:
function WebGLSemantics() {
    return this;
}
WebGLSemantics.prototype = {
    version: 1
};
// Constructor function
WebGLSemantics.create = function webGLSemanticsCreateFn(gd, attributes) {
    var semantics = new WebGLSemantics();
    var numAttributes = attributes.length;
    semantics.length = numAttributes;
    for(var i = 0; i < numAttributes; i += 1) {
        var attribute = attributes[i];
        if(typeof attribute === "string") {
            semantics[i] = gd['SEMANTIC_' + attribute];
        } else {
            semantics[i] = attribute;
        }
    }
    return semantics;
};

function WebGLVertexBuffer() {
    return this;
}
WebGLVertexBuffer.prototype = {
    version: 1,
    map: function vertexBufferMapFn(offset, numVertices) {
        if(offset === undefined) {
            offset = 0;
        }
        if(numVertices === undefined) {
            numVertices = this.numVertices;
        }
        var gd = this.gd;
        var gl = gd.gl;
        var numValuesPerVertex = this.stride;
        var attributes = this.attributes;
        var numAttributes = attributes.length;
        var data, writer;
        var numValues = 0;
        if(this.hasSingleFormat) {
            var maxNumValues = (numVertices * numValuesPerVertex);
            var format = attributes[0].format;
            if(format === gl.BYTE) {
                data = new Int8Array(maxNumValues);
            } else if(format === gl.UNSIGNED_BYTE) {
                data = new Uint8Array(maxNumValues);
            } else if(format === gl.SHORT) {
                data = new Int16Array(maxNumValues);
            } else if(format === gl.UNSIGNED_SHORT) {
                data = new Uint16Array(maxNumValues);
            } else if(format === gl.INT) {
                data = new Int32Array(maxNumValues);
            } else if(format === gl.UNSIGNED_INT) {
                data = new Uint32Array(maxNumValues);
            } else if(format === gl.FLOAT) {
                data = new Float32Array(maxNumValues);
            }
            writer = function vertexBufferWriterSingleFn() {
                var numArguments = arguments.length;
                var currentArgument = 0;
                for(var a = 0; a < numAttributes; a += 1) {
                    var attribute = attributes[a];
                    var numComponents = attribute.numComponents;
                    var currentComponent = 0, j;
                    do {
                        if(currentArgument < numArguments) {
                            var value = arguments[currentArgument];
                            currentArgument += 1;
                            if(typeof value === "number") {
                                if(attribute.normalized) {
                                    value *= attribute.normalizationScale;
                                }
                                data[numValues] = value;
                                numValues += 1;
                                currentComponent += 1;
                            } else if(currentComponent === 0) {
                                var numSubArguments = value.length;
                                if(numSubArguments > numComponents) {
                                    numSubArguments = numComponents;
                                }
                                if(attribute.normalized) {
                                    var scale = attribute.normalizationScale;
                                    for(j = 0; j < numSubArguments; j += 1) {
                                        data[numValues] = (value[j] * scale);
                                        numValues += 1;
                                        currentComponent += 1;
                                    }
                                } else {
                                    for(j = 0; j < numSubArguments; j += 1) {
                                        data[numValues] = value[j];
                                        numValues += 1;
                                        currentComponent += 1;
                                    }
                                }
                                while(currentComponent < numComponents) {
                                    // No need to clear to zeros
                                    numValues += 1;
                                    currentComponent += 1;
                                }
                                break;
                            } else {
                                (TurbulenzEngine).callOnError('Missing values for attribute ' + a);
                                return null;
                            }
                        } else {
                            // No need to clear to zeros
                            numValues += 1;
                            currentComponent += 1;
                        }
                    }while(currentComponent < numComponents);
                }
            };
        } else {
            var destOffset = 0;
            var bufferSize = (numVertices * this.strideInBytes);
            data = new ArrayBuffer(bufferSize);
            if(typeof DataView !== 'undefined' && 'setFloat32' in DataView.prototype) {
                var dataView = new DataView(data);
                writer = function vertexBufferWriterDataViewFn() {
                    var numArguments = arguments.length;
                    var currentArgument = 0;
                    for(var a = 0; a < numAttributes; a += 1) {
                        var attribute = attributes[a];
                        var numComponents = attribute.numComponents;
                        var setter = attribute.typedSetter;
                        var componentStride = attribute.componentStride;
                        var currentComponent = 0, j;
                        do {
                            if(currentArgument < numArguments) {
                                var value = arguments[currentArgument];
                                currentArgument += 1;
                                if(typeof value === "number") {
                                    if(attribute.normalized) {
                                        value *= attribute.normalizationScale;
                                    }
                                    setter.call(dataView, destOffset, value, true);
                                    destOffset += componentStride;
                                    currentComponent += 1;
                                    numValues += 1;
                                } else if(currentComponent === 0) {
                                    var numSubArguments = value.length;
                                    if(numSubArguments > numComponents) {
                                        numSubArguments = numComponents;
                                    }
                                    if(attribute.normalized) {
                                        var scale = attribute.normalizationScale;
                                        for(j = 0; j < numSubArguments; j += 1) {
                                            setter.call(dataView, destOffset, (value[j] * scale), true);
                                            destOffset += componentStride;
                                            currentComponent += 1;
                                            numValues += 1;
                                        }
                                    } else {
                                        for(j = 0; j < numSubArguments; j += 1) {
                                            setter.call(dataView, destOffset, value[j], true);
                                            destOffset += componentStride;
                                            currentComponent += 1;
                                            numValues += 1;
                                        }
                                    }
                                    while(currentComponent < numComponents) {
                                        // No need to clear to zeros
                                        numValues += 1;
                                        currentComponent += 1;
                                    }
                                    break;
                                } else {
                                    (TurbulenzEngine).callOnError('Missing values for attribute ' + a);
                                    return null;
                                }
                            } else {
                                // No need to clear to zeros
                                numValues += 1;
                                currentComponent += 1;
                            }
                        }while(currentComponent < numComponents);
                    }
                };
            } else {
                writer = function vertexBufferWriterMultiFn() {
                    var numArguments = arguments.length;
                    var currentArgument = 0;
                    var dest;
                    for(var a = 0; a < numAttributes; a += 1) {
                        var attribute = attributes[a];
                        var numComponents = attribute.numComponents;
                        dest = new attribute.typedArray(data, destOffset, numComponents);
                        destOffset += attribute.stride;
                        var currentComponent = 0, j;
                        do {
                            if(currentArgument < numArguments) {
                                var value = arguments[currentArgument];
                                currentArgument += 1;
                                if(typeof value === "number") {
                                    if(attribute.normalized) {
                                        value *= attribute.normalizationScale;
                                    }
                                    dest[currentComponent] = value;
                                    currentComponent += 1;
                                    numValues += 1;
                                } else if(currentComponent === 0) {
                                    var numSubArguments = value.length;
                                    if(numSubArguments > numComponents) {
                                        numSubArguments = numComponents;
                                    }
                                    if(attribute.normalized) {
                                        var scale = attribute.normalizationScale;
                                        for(j = 0; j < numSubArguments; j += 1) {
                                            dest[currentComponent] = (value[j] * scale);
                                            currentComponent += 1;
                                            numValues += 1;
                                        }
                                    } else {
                                        for(j = 0; j < numSubArguments; j += 1) {
                                            dest[currentComponent] = value[j];
                                            currentComponent += 1;
                                            numValues += 1;
                                        }
                                    }
                                    while(currentComponent < numComponents) {
                                        // No need to clear to zeros
                                        currentComponent += 1;
                                        numValues += 1;
                                    }
                                    break;
                                } else {
                                    (TurbulenzEngine).callOnError('Missing values for attribute ' + a);
                                    return null;
                                }
                            } else {
                                // No need to clear to zeros
                                currentComponent += 1;
                                numValues += 1;
                            }
                        }while(currentComponent < numComponents);
                    }
                };
            }
        }
        writer.data = data;
        writer.offset = offset;
        writer.getNumWrittenVertices = function getNumWrittenVerticesFn() {
            return Math.floor(numValues / numValuesPerVertex);
        };
        writer.getNumWrittenValues = function getNumWrittenValuesFn() {
            return numValues;
        };
        writer.write = writer;
        return writer;
    },
    unmap: function vertexBufferUnmapFn(writer) {
        if(writer) {
            var data = writer.data;
            delete writer.data;
            delete writer.write;
            var numVertices = writer.getNumWrittenVertices();
            if(!numVertices) {
                return;
            }
            var offset = writer.offset;
            var stride = this.strideInBytes;
            if(this.hasSingleFormat) {
                var numValues = writer.getNumWrittenValues();
                if(numValues < data.length) {
                    data = data.subarray(0, numValues);
                }
            } else {
                var numBytes = (numVertices * stride);
                if(numBytes < data.byteLength) {
                    data = data.slice(0, numBytes);
                }
            }
            var gd = this.gd;
            var gl = gd.gl;
            gd.bindVertexBuffer(this.glBuffer);
            if(numVertices < this.numVertices) {
                gl.bufferSubData(gl.ARRAY_BUFFER, (offset * stride), data);
            } else {
                gl.bufferData(gl.ARRAY_BUFFER, data, this.usage);
            }
        }
    },
    setData: function vertexBufferSetDataFn(data, offset, numVertices) {
        if(offset === undefined) {
            offset = 0;
        }
        if(numVertices === undefined) {
            numVertices = this.numVertices;
        }
        var gd = this.gd;
        var gl = gd.gl;
        var strideInBytes = this.strideInBytes;
        // Fast path for ArrayBuffer data
        if(data.constructor === ArrayBuffer) {
            gd.bindVertexBuffer(this.glBuffer);
            if(numVertices < this.numVertices) {
                gl.bufferSubData(gl.ARRAY_BUFFER, (offset * strideInBytes), data);
            } else {
                gl.bufferData(gl.ARRAY_BUFFER, data, this.usage);
            }
            return;
        }
        var attributes = this.attributes;
        var numAttributes = this.numAttributes;
        var attribute, format, bufferData, TypedArrayConstructor;
        if(this.hasSingleFormat) {
            attribute = attributes[0];
            format = attribute.format;
            if(format === gl.BYTE) {
                if(!(data instanceof Int8Array)) {
                    TypedArrayConstructor = Int8Array;
                }
            } else if(format === gl.UNSIGNED_BYTE) {
                if(!(data instanceof Uint8Array)) {
                    TypedArrayConstructor = Uint8Array;
                }
            } else if(format === gl.SHORT) {
                if(!(data instanceof Int16Array)) {
                    TypedArrayConstructor = Int16Array;
                }
            } else if(format === gl.UNSIGNED_SHORT) {
                if(!(data instanceof Uint16Array)) {
                    TypedArrayConstructor = Uint16Array;
                }
            } else if(format === gl.INT) {
                if(!(data instanceof Int32Array)) {
                    TypedArrayConstructor = Int32Array;
                }
            } else if(format === gl.UNSIGNED_INT) {
                if(!(data instanceof Uint32Array)) {
                    TypedArrayConstructor = Uint32Array;
                }
            } else if(format === gl.FLOAT) {
                if(!(data instanceof Float32Array)) {
                    TypedArrayConstructor = Float32Array;
                }
            }
            var numValuesPerVertex = this.stride;
            var numValues = (numVertices * numValuesPerVertex);
            if(TypedArrayConstructor) {
                // Data has to be put into a Typed Array and
                // potentially normalized.
                if(attribute.normalized) {
                    data = this.scaleValues(data, attribute.normalizationScale, numValues);
                }
                bufferData = new TypedArrayConstructor(data);
                if(numValues < bufferData.length) {
                    bufferData = bufferData.subarray(0, numValues);
                }
            } else {
                bufferData = data;
            }
            if(numValues < data.length) {
                bufferData = bufferData.subarray(0, numValues);
            }
        } else {
            var bufferSize = (numVertices * strideInBytes);
            bufferData = new ArrayBuffer(bufferSize);
            var srcOffset = 0, destOffset = 0, v, c, a, numComponents, componentStride, scale;
            if(typeof DataView !== 'undefined' && 'setFloat32' in DataView.prototype) {
                var dataView = new DataView(bufferData);
                for(v = 0; v < numVertices; v += 1) {
                    for(a = 0; a < numAttributes; a += 1) {
                        attribute = attributes[a];
                        numComponents = attribute.numComponents;
                        componentStride = attribute.componentStride;
                        var setter = attribute.typedSetter;
                        if(attribute.normalized) {
                            scale = attribute.normalizationScale;
                            for(c = 0; c < numComponents; c += 1) {
                                setter.call(dataView, destOffset, (data[srcOffset] * scale), true);
                                destOffset += componentStride;
                                srcOffset += 1;
                            }
                        } else {
                            for(c = 0; c < numComponents; c += 1) {
                                setter.call(dataView, destOffset, data[srcOffset], true);
                                destOffset += componentStride;
                                srcOffset += 1;
                            }
                        }
                    }
                }
            } else {
                for(v = 0; v < numVertices; v += 1) {
                    for(a = 0; a < numAttributes; a += 1) {
                        attribute = attributes[a];
                        numComponents = attribute.numComponents;
                        var dest = new attribute.typedArray(bufferData, destOffset, numComponents);
                        destOffset += attribute.stride;
                        if(attribute.normalized) {
                            scale = attribute.normalizationScale;
                            for(c = 0; c < numComponents; c += 1) {
                                dest[c] = (data[srcOffset] * scale);
                                srcOffset += 1;
                            }
                        } else {
                            for(c = 0; c < numComponents; c += 1) {
                                dest[c] = data[srcOffset];
                                srcOffset += 1;
                            }
                        }
                    }
                }
            }
        }
        data = undefined;
        gd.bindVertexBuffer(this.glBuffer);
        if(numVertices < this.numVertices) {
            gl.bufferSubData(gl.ARRAY_BUFFER, (offset * strideInBytes), bufferData);
        } else {
            gl.bufferData(gl.ARRAY_BUFFER, bufferData, this.usage);
        }
    },
    scaleValues: // Internal
    function scaleValuesFn(values, scale, numValues) {
        if(numValues === undefined) {
            numValues = values.length;
        }
        var scaledValues = new values.constructor(numValues);
        for(var n = 0; n < numValues; n += 1) {
            scaledValues[n] = (values[n] * scale);
        }
        return scaledValues;
    },
    bindAttributes: function bindAttributesFn(numAttributes, attributes, offset) {
        var gd = this.gd;
        var gl = gd.gl;
        var vertexAttributes = this.attributes;
        var stride = this.strideInBytes;
        var attributeMask = 0;
        /*jshint bitwise: false*/
        for(var n = 0; n < numAttributes; n += 1) {
            var vertexAttribute = vertexAttributes[n];
            var attribute = attributes[n];
            attributeMask |= (1 << attribute);
            gl.vertexAttribPointer(attribute, vertexAttribute.numComponents, vertexAttribute.format, vertexAttribute.normalized, stride, offset);
            offset += vertexAttribute.stride;
        }
        /*jshint bitwise: true*/
        return attributeMask;
    },
    setAttributes: function setAttributesFn(attributes) {
        var gd = this.gd;
        var numAttributes = attributes.length;
        this.numAttributes = numAttributes;
        this.attributes = [];
        var stride = 0, numValuesPerVertex = 0, hasSingleFormat = true;
        for(var i = 0; i < numAttributes; i += 1) {
            var format = attributes[i];
            if(typeof format === "string") {
                format = gd['VERTEXFORMAT_' + format];
            }
            this.attributes[i] = format;
            stride += format.stride;
            numValuesPerVertex += format.numComponents;
            if(hasSingleFormat && i) {
                if(format.format !== this.attributes[i - 1].format) {
                    hasSingleFormat = false;
                }
            }
        }
        this.strideInBytes = stride;
        this.stride = numValuesPerVertex;
        this.hasSingleFormat = hasSingleFormat;
        return stride;
    },
    resize: function resizeFn(size) {
        if(size !== (this.strideInBytes * this.numVertices)) {
            var gd = this.gd;
            var gl = gd.gl;
            gd.bindVertexBuffer(this.glBuffer);
            var bufferType = gl.ARRAY_BUFFER;
            gl.bufferData(bufferType, size, this.usage);
            var bufferSize = gl.getBufferParameter(bufferType, gl.BUFFER_SIZE);
            this.numVertices = Math.floor(bufferSize / this.strideInBytes);
        }
    },
    destroy: function vertexBufferDestroyFn() {
        var gd = this.gd;
        if(gd) {
            var glBuffer = this.glBuffer;
            if(glBuffer) {
                var gl = gd.gl;
                if(gl) {
                    gd.unbindVertexBuffer(glBuffer);
                    gl.deleteBuffer(glBuffer);
                }
                delete this.glBuffer;
            }
            delete this.gd;
        }
    }
};
// Constructor function
WebGLVertexBuffer.create = function webGLVertexBufferCreateFn(gd, params) {
    var gl = gd.gl;
    var vb = new WebGLVertexBuffer();
    vb.gd = gd;
    var numVertices = params.numVertices;
    vb.numVertices = numVertices;
    var strideInBytes = vb.setAttributes(params.attributes);
    /*jshint sub: true*/
    // Avoid dot notation lookup to prevent Google Closure complaining
    // about transient being a keyword
    vb['transient'] = (params['transient'] || false);
    vb.dynamic = (params.dynamic || vb['transient']);
    vb.usage = (vb['transient'] ? gl.STREAM_DRAW : (vb.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW));
    /*jshint sub: false*/
    vb.glBuffer = gl.createBuffer();
    var bufferSize = (numVertices * strideInBytes);
    if(params.data) {
        vb.setData(params.data, 0, numVertices);
    } else {
        gd.bindVertexBuffer(vb.glBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, bufferSize, vb.usage);
    }
    return vb;
};





function WebGLPass() {
    return this;
}
WebGLPass.prototype = {
    version: 1,
    updateParametersData: function updateParametersDataFn(gd) {
        var gl = gd.gl;
        this.dirty = false;
        // Set parameters
        var parameters = this.parameters;
        for(var p in parameters) {
            if(parameters.hasOwnProperty(p)) {
                var parameter = parameters[p];
                if(parameter.dirty) {
                    parameter.dirty = 0;
                    var paramInfo = parameter.info;
                    var location = parameter.location;
                    if(paramInfo && null !== location) {
                        var parameterValues = paramInfo.values;
                        var numColumns;
                        if(paramInfo.type === 'float') {
                            numColumns = paramInfo.columns;
                            if(4 === numColumns) {
                                gl.uniform4fv(location, parameterValues);
                            } else if(3 === numColumns) {
                                gl.uniform3fv(location, parameterValues);
                            } else if(2 === numColumns) {
                                gl.uniform2fv(location, parameterValues);
                            } else if(1 === paramInfo.rows) {
                                gl.uniform1f(location, parameterValues[0]);
                            } else//if (1 === numColumns)
                             {
                                gl.uniform1fv(location, parameterValues);
                            }
                        } else if(paramInfo.sampler !== undefined) {
                            gd.setTexture(parameter.textureUnit, parameterValues, paramInfo.sampler);
                        } else {
                            numColumns = paramInfo.columns;
                            if(4 === numColumns) {
                                gl.uniform4iv(location, parameterValues);
                            } else if(3 === numColumns) {
                                gl.uniform3iv(location, parameterValues);
                            } else if(2 === numColumns) {
                                gl.uniform2iv(location, parameterValues);
                            } else if(1 === paramInfo.rows) {
                                gl.uniform1i(location, parameterValues[0]);
                            } else//if (1 === numColumns)
                             {
                                gl.uniform1iv(location, parameterValues);
                            }
                        }
                    }
                }
            }
        }
    },
    initializeParameters: function passInitializeParametersFn(gd) {
        var gl = gd.gl;
        var glProgram = this.glProgram;
        gd.setProgram(glProgram);
        var passParameters = this.parameters;
        for(var p in passParameters) {
            if(passParameters.hasOwnProperty(p)) {
                var parameter = passParameters[p];
                var paramInfo = parameter.info;
                if(paramInfo) {
                    var location = gl.getUniformLocation(glProgram, p);
                    if(null !== location) {
                        parameter.location = location;
                        if(paramInfo.sampler) {
                            gl.uniform1i(location, parameter.textureUnit);
                        } else {
                            var parameterValues = paramInfo.values;
                            var numColumns;
                            if(paramInfo.type === 'float') {
                                numColumns = paramInfo.columns;
                                if(4 === numColumns) {
                                    gl.uniform4fv(location, parameterValues);
                                } else if(3 === numColumns) {
                                    gl.uniform3fv(location, parameterValues);
                                } else if(2 === numColumns) {
                                    gl.uniform2fv(location, parameterValues);
                                } else if(1 === paramInfo.rows) {
                                    gl.uniform1f(location, parameterValues[0]);
                                } else//if (1 === numColumns)
                                 {
                                    gl.uniform1fv(location, parameterValues);
                                }
                            } else {
                                numColumns = paramInfo.columns;
                                if(4 === numColumns) {
                                    gl.uniform4iv(location, parameterValues);
                                } else if(3 === numColumns) {
                                    gl.uniform3iv(location, parameterValues);
                                } else if(2 === numColumns) {
                                    gl.uniform2iv(location, parameterValues);
                                } else if(1 === paramInfo.rows) {
                                    gl.uniform1i(location, parameterValues[0]);
                                } else//if (1 === numColumns)
                                 {
                                    gl.uniform1iv(location, parameterValues);
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    destroy: function passDestroyFn() {
        delete this.glProgram;
        delete this.semanticsMask;
        delete this.parameters;
        var states = this.states;
        if(states) {
            states.length = 0;
            delete this.states;
        }
    }
};
// Constructor function
WebGLPass.create = function webGLPassCreateFn(gd, shader, params) {
    var gl = gd.gl;
    var pass = new WebGLPass();
    pass.name = (params.name || null);
    var programs = shader.programs;
    var parameters = shader.parameters;
    var parameterNames = params.parameters;
    var programNames = params.programs;
    var semanticNames = params.semantics;
    var states = params.states;
    var compoundProgramName = programNames.join(':');
    var linkedProgram = shader.linkedPrograms[compoundProgramName];
    var glProgram, semanticsMask, p, s;
    if(linkedProgram === undefined) {
        // Create GL program
        glProgram = gl.createProgram();
        var numPrograms = programNames.length;
        for(p = 0; p < numPrograms; p += 1) {
            var glShader = programs[programNames[p]];
            if(glShader) {
                gl.attachShader(glProgram, glShader);
            }
        }
        /*jshint bitwise: false*/
        var numSemantics = semanticNames.length;
        semanticsMask = 0;
        for(s = 0; s < numSemantics; s += 1) {
            var semanticName = semanticNames[s];
            var attribute = gd['SEMANTIC_' + semanticName];
            if(attribute !== undefined) {
                semanticsMask |= (1 << attribute);
                gl.bindAttribLocation(glProgram, attribute, ("ATTR" + attribute));
            }
        }
        /*jshint bitwise: true*/
        gl.linkProgram(glProgram);
        shader.linkedPrograms[compoundProgramName] = {
            glProgram: glProgram,
            semanticsMask: semanticsMask
        };
    } else {
        //console.log('Reused program ' + compoundProgramName);
        glProgram = linkedProgram.glProgram;
        semanticsMask = linkedProgram.semanticsMask;
    }
    pass.glProgram = glProgram;
    pass.semanticsMask = semanticsMask;
    // Set parameters
    var numTextureUnits = 0;
    var passParameters = {
    };
    pass.parameters = passParameters;
    var numParameters = parameterNames ? parameterNames.length : 0;
    for(p = 0; p < numParameters; p += 1) {
        var parameterName = parameterNames[p];
        var parameter = {
        };
        passParameters[parameterName] = parameter;
        var paramInfo = parameters[parameterName];
        parameter.info = paramInfo;
        if(paramInfo) {
            parameter.location = null;
            if(paramInfo.sampler) {
                parameter.textureUnit = numTextureUnits;
                numTextureUnits += 1;
            } else {
                parameter.textureUnit = undefined;
            }
        }
    }
    pass.numTextureUnits = numTextureUnits;
    pass.numParameters = numParameters;
    function equalRenderStates(defaultValues, values) {
        var numDefaultValues = defaultValues.length;
        var n;
        for(n = 0; n < numDefaultValues; n += 1) {
            if(defaultValues[n] !== values[n]) {
                return false;
            }
        }
        return true;
    }
    var stateHandlers = gd.stateHandlers;
    var passStates = [];
    var passStatesSet = {
    };
    pass.states = passStates;
    pass.statesSet = passStatesSet;
    for(s in states) {
        if(states.hasOwnProperty(s)) {
            var stateHandler = stateHandlers[s];
            if(stateHandler) {
                var values = stateHandler.parse(states[s]);
                if(values !== null) {
                    if(equalRenderStates(stateHandler.defaultValues, values)) {
                        continue;
                    }
                    passStates.push({
                        name: s,
                        set: stateHandler.set,
                        reset: stateHandler.reset,
                        values: values
                    });
                    passStatesSet[s] = true;
                } else {
                    (TurbulenzEngine).callOnError('Unknown value for state ' + s + ': ' + states[s]);
                }
            }
        }
    }
    return pass;
};
//
// Technique
//
function Technique() {
    return this;
}
Technique.prototype = {
    version: 1,
    getPass: function getPassFn(id) {
        var passes = this.passes;
        var numPasses = passes.length;
        if(typeof id === "string") {
            for(var n = 0; n < numPasses; n += 1) {
                var pass = passes[n];
                if(pass.name === id) {
                    return pass;
                }
            }
        } else {
            /*jshint bitwise: false*/
            id = (id | 0);
            /*jshint bitwise: true*/
            if(id < numPasses) {
                return passes[id];
            }
        }
        return null;
    },
    activate: function activateFn(gd) {
        this.device = gd;
        if(!this.initialized) {
            this.shader.initialize(gd);
            this.initialize(gd);
        }
        if(debug) {
            gd.metrics.techniqueChanges += 1;
        }
    },
    deactivate: function deactivateFn() {
        this.device = null;
    },
    checkProperties: function checkPropertiesFn(gd) {
        // Check for parameters set directly into the technique...
                var fakeTechniqueParameters = {
        }, p;
        for(p in this) {
            if(p !== 'version' && p !== 'name' && p !== 'passes' && p !== 'numPasses' && p !== 'device' && p !== 'numParameters') {
                fakeTechniqueParameters[p] = this[p];
            }
        }
        if(fakeTechniqueParameters) {
            var passes = this.passes;
            if(passes.length === 1) {
                gd.setParametersImmediate(gd, passes, fakeTechniqueParameters);
            } else {
                gd.setParametersDeferred(gd, passes, fakeTechniqueParameters);
            }
            for(p in fakeTechniqueParameters) {
                if(fakeTechniqueParameters.hasOwnProperty(p)) {
                    delete this[p];
                }
            }
        }
    },
    initialize: function techniqueInitializeFn(gd) {
        if(this.initialized) {
            return;
        }
        var passes = this.passes;
        if(passes) {
            var numPasses = passes.length;
            var n;
            for(n = 0; n < numPasses; n += 1) {
                passes[n].initializeParameters(gd);
            }
        }
        if(Object.defineProperty) {
            this.initializeParametersSetters(gd);
        }
        this.initialized = true;
    },
    initializeParametersSetters: function initializeParametersSettersFn(gd) {
        var gl = gd.gl;
        function make_sampler_setter(pass, parameter) {
            return function (parameterValues) {
                if(this.device) {
                    gd.setTexture(parameter.textureUnit, parameterValues, parameter.info.sampler);
                } else {
                    pass.dirty = true;
                    parameter.dirty = 1;
                    parameter.info.values = parameterValues;
                }
            };
        }
        function make_float_uniform_setter(pass, parameter) {
            var paramInfo = parameter.info;
            var location = parameter.location;
            function setDeferredParameter(parameterValues) {
                if(typeof parameterValues !== 'number') {
                    var values = paramInfo.values;
                    var numValues = Math.min(paramInfo.numValues, parameterValues.length);
                    for(var v = 0; v < numValues; v += 1) {
                        values[v] = parameterValues[v];
                    }
                    parameter.dirty = Math.max(numValues, (parameter.dirty || 0));
                } else {
                    paramInfo.values[0] = parameterValues;
                    parameter.dirty = (parameter.dirty || 1);
                }
                pass.dirty = true;
            }
            switch(paramInfo.columns) {
                case 1:
                    if(1 === paramInfo.numValues) {
                        return function (parameterValues) {
                            if(this.device) {
                                gl.uniform1f(location, parameterValues);
                            } else {
                                setDeferredParameter(parameterValues);
                            }
                        };
                    }
                    return function (parameterValues) {
                        if(this.device) {
                            gl.uniform1fv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 2:
                    return function (parameterValues) {
                        if(this.device) {
                            gl.uniform2fv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 3:
                    return function (parameterValues) {
                        if(this.device) {
                            gl.uniform3fv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 4:
                    return function (parameterValues) {
                        if(this.device) {
                            gl.uniform4fv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                default:
                    return null;
            }
        }
        function make_int_uniform_setter(pass, parameter) {
            var paramInfo = parameter.info;
            var location = parameter.location;
            function setDeferredParameter(parameterValues) {
                if(typeof parameterValues !== 'number') {
                    var values = paramInfo.values;
                    var numValues = Math.min(paramInfo.numValues, parameterValues.length);
                    for(var v = 0; v < numValues; v += 1) {
                        values[v] = parameterValues[v];
                    }
                    parameter.dirty = Math.max(numValues, (parameter.dirty || 0));
                } else {
                    paramInfo.values[0] = parameterValues;
                    parameter.dirty = (parameter.dirty || 1);
                }
                pass.dirty = true;
            }
            switch(paramInfo.columns) {
                case 1:
                    if(1 === paramInfo.numValues) {
                        return function (parameterValues) {
                            if(this.device) {
                                gl.uniform1i(location, parameterValues);
                            } else {
                                setDeferredParameter(parameterValues);
                            }
                        };
                    }
                    return function (parameterValues) {
                        if(this.device) {
                            gl.uniform1iv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 2:
                    return function (parameterValues) {
                        if(this.device) {
                            gl.uniform2iv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 3:
                    return function (parameterValues) {
                        if(this.device) {
                            gl.uniform3iv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 4:
                    return function (parameterValues) {
                        if(this.device) {
                            gl.uniform4iv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                default:
                    return null;
            }
        }
        var passes = this.passes;
        var numPasses = passes.length;
        var pass, parameters, p, parameter, paramInfo, setter;
        if(numPasses === 1) {
            pass = passes[0];
            parameters = pass.parameters;
            for(p in parameters) {
                if(parameters.hasOwnProperty(p)) {
                    parameter = parameters[p];
                    paramInfo = parameter.info;
                    if(paramInfo) {
                        if(undefined !== parameter.location) {
                            if(paramInfo.sampler) {
                                setter = make_sampler_setter(pass, parameter);
                            } else {
                                if(paramInfo.type === 'float') {
                                    setter = make_float_uniform_setter(pass, parameter);
                                } else {
                                    setter = make_int_uniform_setter(pass, parameter);
                                }
                            }
                            Object.defineProperty(this, p, {
                                set: setter,
                                enumerable: false,
                                configurable: false
                            });
                        }
                    }
                }
            }
            this.checkProperties = null;
        } else {
            Object.defineProperty(this, 'device', {
                writable: true,
                enumerable: false,
                configurable: false
            });
            Object.defineProperty(this, 'version', {
                writable: false,
                enumerable: false,
                configurable: false
            });
            Object.defineProperty(this, 'name', {
                writable: false,
                enumerable: false,
                configurable: false
            });
            Object.defineProperty(this, 'passes', {
                writable: false,
                enumerable: false,
                configurable: false
            });
            Object.defineProperty(this, 'numParameters', {
                writable: false,
                enumerable: false,
                configurable: false
            });
        }
    },
    destroy: function techniqueDestroyFn() {
        var passes = this.passes;
        if(passes) {
            var numPasses = passes.length;
            var n;
            for(n = 0; n < numPasses; n += 1) {
                passes[n].destroy();
            }
            passes.length = 0;
            delete this.passes;
        }
        delete this.device;
    }
};
// Constructor function
Technique.create = function webGLTechniqueCreateFn(gd, shader, name, passes) {
    var technique = new Technique();
    technique.initialized = false;
    technique.shader = shader;
    technique.name = name;
    var numPasses = passes.length, n;
    var numParameters = 0;
    technique.passes = [];
    technique.numPasses = numPasses;
    for(n = 0; n < numPasses; n += 1) {
        var passParams = passes[n];
        if(passParams.parameters) {
            numParameters += passParams.parameters.length;
        }
        technique.passes[n] = WebGLPass.create(gd, shader, passParams);
    }
    technique.numParameters = numParameters;
    technique.device = null;
    return technique;
};
//
// Shader
//
function Shader() {
    return this;
}
Shader.prototype = {
    version: 1,
    getTechnique: function getTechniqueFn(name) {
        if(typeof name === "string") {
            return this.techniques[name];
        } else {
            var techniques = this.techniques;
            for(var t in techniques) {
                if(techniques.hasOwnProperty(t)) {
                    if(name === 0) {
                        return techniques[t];
                    } else {
                        name -= 1;
                    }
                }
            }
            return null;
        }
    },
    getParameter: function getParameterFn(name) {
        if(typeof name === "string") {
            return this.parameters[name];
        } else {
            /*jshint bitwise: false*/
            name = (name | 0);
            /*jshint bitwise: true*/
            var parameters = this.parameters;
            for(var p in parameters) {
                if(parameters.hasOwnProperty(p)) {
                    if(name === 0) {
                        return parameters[p];
                    } else {
                        name -= 1;
                    }
                }
            }
            return null;
        }
    },
    initialize: function shaderInitializeFn(gd) {
        if(this.initialized) {
            return;
        }
        var gl = gd.gl;
        var p;
        // Check copmpiled programs as late as possible
        var shaderPrograms = this.programs;
        for(p in shaderPrograms) {
            if(shaderPrograms.hasOwnProperty(p)) {
                var compiledProgram = shaderPrograms[p];
                var compiled = gl.getShaderParameter(compiledProgram, gl.COMPILE_STATUS);
                if(!compiled) {
                    var compilerInfo = gl.getShaderInfoLog(compiledProgram);
                    (TurbulenzEngine).callOnError('Program "' + p + '" failed to compile: ' + compilerInfo);
                }
            }
        }
        // Check linked programs as late as possible
        var linkedPrograms = this.linkedPrograms;
        for(p in linkedPrograms) {
            if(linkedPrograms.hasOwnProperty(p)) {
                var linkedProgram = linkedPrograms[p];
                var glProgram = linkedProgram.glProgram;
                if(glProgram) {
                    var linked = gl.getProgramParameter(glProgram, gl.LINK_STATUS);
                    if(!linked) {
                        var linkerInfo = gl.getProgramInfoLog(glProgram);
                        (TurbulenzEngine).callOnError('Program "' + p + '" failed to link: ' + linkerInfo);
                    }
                }
            }
        }
        this.initialized = true;
    },
    destroy: function shaderDestroyFn() {
        var gd = this.gd;
        if(gd) {
            var gl = gd.gl;
            var p;
            var techniques = this.techniques;
            if(techniques) {
                for(p in techniques) {
                    if(techniques.hasOwnProperty(p)) {
                        techniques[p].destroy();
                    }
                }
                delete this.techniques;
            }
            var linkedPrograms = this.linkedPrograms;
            if(linkedPrograms) {
                if(gl) {
                    for(p in linkedPrograms) {
                        if(linkedPrograms.hasOwnProperty(p)) {
                            var linkedProgram = linkedPrograms[p];
                            var glProgram = linkedProgram.glProgram;
                            if(glProgram) {
                                gl.deleteProgram(glProgram);
                                delete linkedProgram.glProgram;
                            }
                        }
                    }
                }
                delete this.linkedPrograms;
            }
            var programs = this.programs;
            if(programs) {
                if(gl) {
                    for(p in programs) {
                        if(programs.hasOwnProperty(p)) {
                            gl.deleteShader(programs[p]);
                        }
                    }
                }
                delete this.programs;
            }
            delete this.samplers;
            delete this.parameters;
            delete this.gd;
        }
    }
};
// Constructor function
Shader.create = function webGLShaderCreateFn(gd, params) {
    var gl = gd.gl;
    var shader = new Shader();
    shader.initialized = false;
    var techniques = params.techniques;
    var parameters = params.parameters;
    var programs = params.programs;
    var samplers = params.samplers;
    var p;
    shader.gd = gd;
    shader.name = params.name;
    // Compile programs as early as possible
    var shaderPrograms = {
    };
    shader.programs = shaderPrograms;
    for(p in programs) {
        if(programs.hasOwnProperty(p)) {
            var program = programs[p];
            var glShaderType;
            if(program.type === 'fragment') {
                glShaderType = gl.FRAGMENT_SHADER;
            } else if(program.type === 'vertex') {
                glShaderType = gl.VERTEX_SHADER;
            }
            var glShader = gl.createShader(glShaderType);
            gl.shaderSource(glShader, program.code);
            gl.compileShader(glShader);
            shaderPrograms[p] = glShader;
        }
    }
    var linkedPrograms = {
    };
    shader.linkedPrograms = linkedPrograms;
    // Samplers
    var defaultSampler = gd.DEFAULT_SAMPLER;
    var maxAnisotropy = gd.maxAnisotropy;
    shader.samplers = {
    };
    var sampler;
    for(p in samplers) {
        if(samplers.hasOwnProperty(p)) {
            sampler = samplers[p];
            var samplerMaxAnisotropy = sampler.MaxAnisotropy;
            if(samplerMaxAnisotropy) {
                if(samplerMaxAnisotropy > maxAnisotropy) {
                    samplerMaxAnisotropy = maxAnisotropy;
                }
            } else {
                samplerMaxAnisotropy = defaultSampler.maxAnisotropy;
            }
            sampler = {
                minFilter: (sampler.MinFilter || defaultSampler.minFilter),
                magFilter: (sampler.MagFilter || defaultSampler.magFilter),
                wrapS: (sampler.WrapS || defaultSampler.wrapS),
                wrapT: (sampler.WrapT || defaultSampler.wrapT),
                wrapR: (sampler.WrapR || defaultSampler.wrapR),
                maxAnisotropy: samplerMaxAnisotropy
            };
            if(sampler.wrapS === 0x2900) {
                sampler.wrapS = gl.CLAMP_TO_EDGE;
            }
            if(sampler.wrapT === 0x2900) {
                sampler.wrapT = gl.CLAMP_TO_EDGE;
            }
            if(sampler.wrapR === 0x2900) {
                sampler.wrapR = gl.CLAMP_TO_EDGE;
            }
            shader.samplers[p] = gd.createSampler(sampler);
        }
    }
    // Parameters
    var numParameters = 0;
    shader.parameters = {
    };
    for(p in parameters) {
        if(parameters.hasOwnProperty(p)) {
            var parameter = parameters[p];
            if(!parameter.columns) {
                parameter.columns = 1;
            }
            if(!parameter.rows) {
                parameter.rows = 1;
            }
            parameter.numValues = (parameter.columns * parameter.rows);
            var parameterType = parameter.type;
            if(parameterType === "float" || parameterType === "int" || parameterType === "bool") {
                var parameterValues = parameter.values;
                if(parameterValues) {
                    if(parameterType === "float") {
                        parameter.values = new Float32Array(parameterValues);
                    } else {
                        parameter.values = new Int32Array(parameterValues);
                    }
                } else {
                    if(parameterType === "float") {
                        parameter.values = new Float32Array(parameter.numValues);
                    } else {
                        parameter.values = new Int32Array(parameter.numValues);
                    }
                }
                parameter.sampler = undefined;
            } else// Sampler
             {
                sampler = shader.samplers[p];
                if(!sampler) {
                    sampler = defaultSampler;
                    shader.samplers[p] = defaultSampler;
                }
                parameter.sampler = sampler;
                parameter.values = null;
            }
            parameter.name = p;
            shader.parameters[p] = parameter;
            numParameters += 1;
        }
    }
    shader.numParameters = numParameters;
    // Techniques and passes
    var shaderTechniques = {
    };
    var numTechniques = 0;
    shader.techniques = shaderTechniques;
    for(p in techniques) {
        if(techniques.hasOwnProperty(p)) {
            shaderTechniques[p] = Technique.create(gd, shader, p, techniques[p]);
            numTechniques += 1;
        }
    }
    shader.numTechniques = numTechniques;
    return shader;
};

function TechniqueParameters() {
    return this;
}
// Constructor function
TechniqueParameters.create = function TechniqueParametersFn(params) {
    var techniqueParameters = new TechniqueParameters();
    if(params) {
        for(var p in params) {
            if(params.hasOwnProperty(p)) {
                techniqueParameters[p] = params[p];
            }
        }
    }
    return techniqueParameters;
};
//
// TechniqueParameterBuffer
//
var techniqueParameterBufferCreate = function techniqueParameterBufferCreateFn(params) {
    if(Float32Array.prototype.map === undefined) {
        Float32Array.prototype.map = function techniqueParameterBufferMap(offset, numFloats) {
            if(offset === undefined) {
                offset = 0;
            }
            var buffer = this;
            if(numFloats === undefined) {
                numFloats = this.length;
            }
            function techniqueParameterBufferWriter() {
                var numArguments = arguments.length;
                for(var a = 0; a < numArguments; a += 1) {
                    var value = arguments[a];
                    if(typeof value === 'number') {
                        buffer[offset] = value;
                        offset += 1;
                    } else {
                        buffer.setData(value, offset, value.length);
                        offset += value.length;
                    }
                }
            }
            return techniqueParameterBufferWriter;
        };
        Float32Array.prototype.unmap = function techniqueParameterBufferUnmap() {
            /* writer */         };
        Float32Array.prototype.setData = function techniqueParameterBufferSetData(data, offset, numValues) {
            if(offset === undefined) {
                offset = 0;
            }
            if(numValues === undefined) {
                numValues = this.length;
            }
            for(var n = 0; n < numValues; n += 1 , offset += 1) {
                this[offset] = data[n];
            }
        };
    }
    return new Float32Array(params.numFloats);
};

function DrawParameters() {
    // Streams, TechniqueParameters and Instances are stored as indexed properties
    this.endStreams = 0;
    this.endTechniqueParameters = (16 * 3);
    this.endInstances = ((16 * 3) + 8);
    this.firstIndex = 0;
    this.count = 0;
    this.sortKey = 0;
    this.technique = null;
    this.indexBuffer = null;
    this.primitive = -1;
    this.userData = null;
    // Initialize for 1 Stream, 2 TechniqueParameters and 8 Instances
    this[0] = undefined;
    this[1] = undefined;
    this[2] = undefined;
    this[(16 * 3) + 0] = undefined;
    this[(16 * 3) + 1] = undefined;
    this[((16 * 3) + 8) + 0] = undefined;
    this[((16 * 3) + 8) + 1] = undefined;
    this[((16 * 3) + 8) + 2] = undefined;
    this[((16 * 3) + 8) + 3] = undefined;
    this[((16 * 3) + 8) + 4] = undefined;
    this[((16 * 3) + 8) + 5] = undefined;
    this[((16 * 3) + 8) + 6] = undefined;
    this[((16 * 3) + 8) + 7] = undefined;
    return this;
}
DrawParameters.prototype = {
    version: 1,
    setTechniqueParameters: function setTechniqueParametersFn(indx, techniqueParameters) {
        if(indx < 8) {
            indx += (16 * 3);
            this[indx] = techniqueParameters;
            var endTechniqueParameters = this.endTechniqueParameters;
            if(techniqueParameters) {
                if(endTechniqueParameters <= indx) {
                    this.endTechniqueParameters = (indx + 1);
                }
            } else {
                while((16 * 3) < endTechniqueParameters && !this[endTechniqueParameters - 1]) {
                    endTechniqueParameters -= 1;
                }
                this.endTechniqueParameters = endTechniqueParameters;
            }
        }
    },
    setVertexBuffer: function setVertexBufferFn(indx, vertexBuffer) {
        if(indx < 16) {
            indx *= 3;
            this[indx] = vertexBuffer;
            var endStreams = this.endStreams;
            if(vertexBuffer) {
                if(endStreams <= indx) {
                    this.endStreams = (indx + 3);
                }
            } else {
                while(0 < endStreams && !this[endStreams - 3]) {
                    endStreams -= 3;
                }
                this.endStreams = endStreams;
            }
        }
    },
    setSemantics: function setSemanticsFn(indx, semantics) {
        if(indx < 16) {
            this[(indx * 3) + 1] = semantics;
        }
    },
    setOffset: function setOffsetFn(indx, offset) {
        if(indx < 16) {
            this[(indx * 3) + 2] = offset;
        }
    },
    getTechniqueParameters: function getTechniqueParametersFn(indx) {
        if(indx < 8) {
            return this[indx + (16 * 3)];
        } else {
            return undefined;
        }
    },
    getVertexBuffer: function getVertexBufferFn(indx) {
        if(indx < 16) {
            return this[(indx * 3) + 0];
        } else {
            return undefined;
        }
    },
    getSemantics: function getSemanticsFn(indx) {
        if(indx < 16) {
            return this[(indx * 3) + 1];
        } else {
            return undefined;
        }
    },
    getOffset: function getOffsetFn(indx) {
        if(indx < 16) {
            return this[(indx * 3) + 2];
        } else {
            return undefined;
        }
    },
    addInstance: function drawParametersAddInstanceFn(instanceParameters) {
        if(instanceParameters) {
            var endInstances = this.endInstances;
            this.endInstances = (endInstances + 1);
            this[endInstances] = instanceParameters;
        }
    },
    removeInstances: function drawParametersRemoveInstancesFn() {
        this.endInstances = ((16 * 3) + 8);
    },
    getNumInstances: function drawParametersGetNumInstancesFn() {
        return (this.endInstances - ((16 * 3) + 8));
    }
};
// Constructor function
DrawParameters.create = function webGLDrawParametersFn() {
    /* params */ return new DrawParameters();
};




function WebGLGraphicsDevice() {
    return this;
}
WebGLGraphicsDevice.prototype = {
    version: 1,
    SEMANTIC_POSITION: 0,
    SEMANTIC_POSITION0: 0,
    SEMANTIC_BLENDWEIGHT: 1,
    SEMANTIC_BLENDWEIGHT0: 1,
    SEMANTIC_NORMAL: 2,
    SEMANTIC_NORMAL0: 2,
    SEMANTIC_COLOR: 3,
    SEMANTIC_COLOR0: 3,
    SEMANTIC_COLOR1: 4,
    SEMANTIC_SPECULAR: 4,
    SEMANTIC_FOGCOORD: 5,
    SEMANTIC_TESSFACTOR: 5,
    SEMANTIC_PSIZE0: 6,
    SEMANTIC_BLENDINDICES: 7,
    SEMANTIC_BLENDINDICES0: 7,
    SEMANTIC_TEXCOORD: 8,
    SEMANTIC_TEXCOORD0: 8,
    SEMANTIC_TEXCOORD1: 9,
    SEMANTIC_TEXCOORD2: 10,
    SEMANTIC_TEXCOORD3: 11,
    SEMANTIC_TEXCOORD4: 12,
    SEMANTIC_TEXCOORD5: 13,
    SEMANTIC_TEXCOORD6: 14,
    SEMANTIC_TEXCOORD7: 15,
    SEMANTIC_TANGENT: 14,
    SEMANTIC_TANGENT0: 14,
    SEMANTIC_BINORMAL0: 15,
    SEMANTIC_BINORMAL: 15,
    SEMANTIC_PSIZE: 6,
    SEMANTIC_ATTR0: 0,
    SEMANTIC_ATTR1: 1,
    SEMANTIC_ATTR2: 2,
    SEMANTIC_ATTR3: 3,
    SEMANTIC_ATTR4: 4,
    SEMANTIC_ATTR5: 5,
    SEMANTIC_ATTR6: 6,
    SEMANTIC_ATTR7: 7,
    SEMANTIC_ATTR8: 8,
    SEMANTIC_ATTR9: 9,
    SEMANTIC_ATTR10: 10,
    SEMANTIC_ATTR11: 11,
    SEMANTIC_ATTR12: 12,
    SEMANTIC_ATTR13: 13,
    SEMANTIC_ATTR14: 14,
    SEMANTIC_ATTR15: 15,
    PIXELFORMAT_A8: 0,
    PIXELFORMAT_L8: 1,
    PIXELFORMAT_L8A8: 2,
    PIXELFORMAT_R5G5B5A1: 3,
    PIXELFORMAT_R5G6B5: 4,
    PIXELFORMAT_R4G4B4A4: 5,
    PIXELFORMAT_R8G8B8A8: 6,
    PIXELFORMAT_R8G8B8: 7,
    PIXELFORMAT_D24S8: 8,
    PIXELFORMAT_D16: 9,
    PIXELFORMAT_DXT1: 10,
    PIXELFORMAT_DXT3: 11,
    PIXELFORMAT_DXT5: 12,
    drawIndexed: function drawIndexedFn(primitive, numIndices, first) {
        var gl = this.gl;
        var indexBuffer = this.activeIndexBuffer;
        var offset;
        if(first) {
            offset = (first * indexBuffer.stride);
        } else {
            offset = 0;
        }
        var format = indexBuffer.format;
        var attributeMask = this.attributeMask;
        var activeTechnique = this.activeTechnique;
        var passes = activeTechnique.passes;
        var numPasses = passes.length;
        var mask;
        if(activeTechnique.checkProperties) {
            activeTechnique.checkProperties(this);
        }
        /*jshint bitwise: false*/
        if(1 === numPasses) {
            mask = (passes[0].semanticsMask & attributeMask);
            if(mask !== this.clientStateMask) {
                this.enableClientState(mask);
            }
            gl.drawElements(primitive, numIndices, format, offset);
            if(debug) {
                this.metrics.addPrimitives(primitive, numIndices);
            }
        } else {
            for(var p = 0; p < numPasses; p += 1) {
                var pass = passes[p];
                mask = (pass.semanticsMask & attributeMask);
                if(mask !== this.clientStateMask) {
                    this.enableClientState(mask);
                }
                this.setPass(pass);
                gl.drawElements(primitive, numIndices, format, offset);
                if(debug) {
                    this.metrics.addPrimitives(primitive, numIndices);
                }
            }
        }
        /*jshint bitwise: true*/
            },
    draw: function drawFn(primitive, numVertices, first) {
        var gl = this.gl;
        var attributeMask = this.attributeMask;
        var activeTechnique = this.activeTechnique;
        var passes = activeTechnique.passes;
        var numPasses = passes.length;
        var mask;
        if(activeTechnique.checkProperties) {
            activeTechnique.checkProperties(this);
        }
        /*jshint bitwise: false*/
        if(1 === numPasses) {
            mask = (passes[0].semanticsMask & attributeMask);
            if(mask !== this.clientStateMask) {
                this.enableClientState(mask);
            }
            gl.drawArrays(primitive, first, numVertices);
            if(debug) {
                this.metrics.addPrimitives(primitive, numVertices);
            }
        } else {
            for(var p = 0; p < numPasses; p += 1) {
                var pass = passes[p];
                mask = (pass.semanticsMask & attributeMask);
                if(mask !== this.clientStateMask) {
                    this.enableClientState(mask);
                }
                this.setPass(pass);
                gl.drawArrays(primitive, first, numVertices);
                if(debug) {
                    this.metrics.addPrimitives(primitive, numVertices);
                }
            }
        }
        /*jshint bitwise: true*/
            },
    setTechniqueParameters: function setTechniqueParametersFn() {
        var activeTechnique = this.activeTechnique;
        var passes = activeTechnique.passes;
        var setParameters = (1 === passes.length ? this.setParametersImmediate : this.setParametersDeferred);
        var numTechniqueParameters = arguments.length;
        for(var t = 0; t < numTechniqueParameters; t += 1) {
            setParameters(this, passes, arguments[t]);
        }
    },
    setParametersImmediate: //Internal
    function setParametersImmediateFn(gd, passes, techniqueParameters) {
        var gl = gd.gl;
        var parameters = passes[0].parameters;
        /*jshint forin: true*/
        for(var p in techniqueParameters) {
            var parameter = parameters[p];
            if(parameter !== undefined) {
                var parameterValues = techniqueParameters[p];
                if(parameterValues !== undefined) {
                    var paramInfo = parameter.info;
                    var numColumns, location;
                    if(paramInfo.type === 'float') {
                        numColumns = paramInfo.columns;
                        location = parameter.location;
                        if(4 === numColumns) {
                            gl.uniform4fv(location, parameterValues);
                        } else if(3 === numColumns) {
                            gl.uniform3fv(location, parameterValues);
                        } else if(2 === numColumns) {
                            gl.uniform2fv(location, parameterValues);
                        } else if(1 === paramInfo.rows) {
                            gl.uniform1f(location, parameterValues);
                        } else//if (1 === numColumns)
                         {
                            gl.uniform1fv(location, parameterValues);
                        }
                    } else if(paramInfo.sampler !== undefined) {
                        gd.setTexture(parameter.textureUnit, parameterValues, paramInfo.sampler);
                    } else {
                        numColumns = paramInfo.columns;
                        location = parameter.location;
                        if(4 === numColumns) {
                            gl.uniform4iv(location, parameterValues);
                        } else if(3 === numColumns) {
                            gl.uniform3iv(location, parameterValues);
                        } else if(2 === numColumns) {
                            gl.uniform2iv(location, parameterValues);
                        } else if(1 === paramInfo.rows) {
                            gl.uniform1i(location, parameterValues);
                        } else//if (1 === numColumns)
                         {
                            gl.uniform1iv(location, parameterValues);
                        }
                    }
                } else {
                    delete techniqueParameters[p];
                }
            }
        }
        /*jshint forin: false*/
            },
    setParametersCaching: // ONLY USE FOR SINGLE PASS TECHNIQUES ON DRAWARRAY
    function setParametersCachingFn(gd, passes, techniqueParameters) {
        var gl = gd.gl;
        var parameters = passes[0].parameters;
        /*jshint forin: true*/
        for(var p in techniqueParameters) {
            var parameter = parameters[p];
            if(parameter !== undefined) {
                var parameterValues = techniqueParameters[p];
                if(parameter.value !== parameterValues) {
                    if(parameterValues !== undefined) {
                        parameter.value = parameterValues;
                        var paramInfo = parameter.info;
                        var numColumns, location;
                        if(paramInfo.type === 'float') {
                            numColumns = paramInfo.columns;
                            location = parameter.location;
                            if(4 === numColumns) {
                                gl.uniform4fv(location, parameterValues);
                            } else if(3 === numColumns) {
                                gl.uniform3fv(location, parameterValues);
                            } else if(2 === numColumns) {
                                gl.uniform2fv(location, parameterValues);
                            } else if(1 === paramInfo.rows) {
                                gl.uniform1f(location, parameterValues);
                            } else//if (1 === numColumns)
                             {
                                gl.uniform1fv(location, parameterValues);
                            }
                        } else if(paramInfo.sampler !== undefined) {
                            gd.setTexture(parameter.textureUnit, parameterValues, paramInfo.sampler);
                        } else {
                            numColumns = paramInfo.columns;
                            location = parameter.location;
                            if(4 === numColumns) {
                                gl.uniform4iv(location, parameterValues);
                            } else if(3 === numColumns) {
                                gl.uniform3iv(location, parameterValues);
                            } else if(2 === numColumns) {
                                gl.uniform2iv(location, parameterValues);
                            } else if(1 === paramInfo.rows) {
                                gl.uniform1i(location, parameterValues);
                            } else//if (1 === numColumns)
                             {
                                gl.uniform1iv(location, parameterValues);
                            }
                        }
                    } else {
                        delete techniqueParameters[p];
                    }
                }
            }
        }
        /*jshint forin: false*/
            },
    setParametersDeferred: function setParametersDeferredFn(gd, passes, techniqueParameters) {
        var numPasses = passes.length;
        var min = Math.min;
        var max = Math.max;
        for(var n = 0; n < numPasses; n += 1) {
            var pass = passes[n];
            var parameters = pass.parameters;
            pass.dirty = true;
            /*jshint forin: true*/
            for(var p in techniqueParameters) {
                var parameter = parameters[p];
                if(parameter) {
                    var parameterValues = techniqueParameters[p];
                    if(parameterValues !== undefined) {
                        var paramInfo = parameter.info;
                        if(paramInfo.sampler) {
                            paramInfo.values = parameterValues;
                            parameter.dirty = 1;
                        } else if(typeof parameterValues !== 'number') {
                            var values = paramInfo.values;
                            var numValues = min(paramInfo.numValues, parameterValues.length);
                            for(var v = 0; v < numValues; v += 1) {
                                values[v] = parameterValues[v];
                            }
                            parameter.dirty = max(numValues, (parameter.dirty || 0));
                        } else {
                            paramInfo.values[0] = parameterValues;
                            parameter.dirty = (parameter.dirty || 1);
                        }
                    } else {
                        delete techniqueParameters[p];
                    }
                }
            }
            /*jshint forin: false*/
                    }
    },
    setTechnique: function setTechniqueFn(technique) {
        var activeTechnique = this.activeTechnique;
        if(activeTechnique !== technique) {
            if(activeTechnique) {
                activeTechnique.deactivate();
            }
            this.activeTechnique = technique;
            technique.activate(this);
            var passes = technique.passes;
            if(1 === passes.length) {
                this.setPass(passes[0]);
            }
        }
    },
    setTechniqueCaching: // ONLY USE FOR SINGLE PASS TECHNIQUES ON DRAWARRAY
    function setTechniqueCachingFn(technique) {
        var pass = technique.passes[0];
        var activeTechnique = this.activeTechnique;
        if(activeTechnique !== technique) {
            if(activeTechnique) {
                activeTechnique.deactivate();
            }
            this.activeTechnique = technique;
            technique.activate(this);
            this.setPass(pass);
        }
        var parameters = pass.parameters;
        for(var p in parameters) {
            if(parameters.hasOwnProperty(p)) {
                parameters[p].value = null;
            }
        }
    },
    setStream: function setStreamFn(vertexBuffer, semantics, offset) {
        debug.assert(vertexBuffer instanceof WebGLVertexBuffer);
        debug.assert(semantics instanceof WebGLSemantics);
        if(offset) {
            offset *= vertexBuffer.strideInBytes;
        } else {
            offset = 0;
        }
        this.bindVertexBuffer(vertexBuffer.glBuffer);
        var attributes = semantics;
        var numAttributes = attributes.length;
        if(numAttributes > vertexBuffer.numAttributes) {
            numAttributes = vertexBuffer.numAttributes;
        }
        /*jshint bitwise: false*/
        this.attributeMask |= vertexBuffer.bindAttributes(numAttributes, attributes, offset);
        /*jshint bitwise: true*/
            },
    setIndexBuffer: function setIndexBufferFn(indexBuffer) {
        if(this.activeIndexBuffer !== indexBuffer) {
            this.activeIndexBuffer = indexBuffer;
            var glBuffer;
            if(indexBuffer) {
                glBuffer = indexBuffer.glBuffer;
            } else {
                glBuffer = null;
            }
            var gl = this.gl;
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, glBuffer);
            if(debug) {
                this.metrics.indexBufferChanges += 1;
            }
        }
    },
    drawArray: function drawArrayFn(drawParametersArray, globalTechniqueParametersArray, sortMode) {
        var gl = this.gl;
        var ELEMENT_ARRAY_BUFFER = gl.ELEMENT_ARRAY_BUFFER;
        var setParametersCaching = this.setParametersCaching;
        var setParametersDeferred = this.setParametersDeferred;
        var numGlobalTechniqueParameters = globalTechniqueParametersArray.length;
        var numDrawParameters = drawParametersArray.length;
        if(numDrawParameters > 1 && sortMode) {
            if(sortMode > 0) {
                drawParametersArray.sort(function drawArraySortPositive(a, b) {
                    return (b.sortKey - a.sortKey);
                });
            } else//if (sortMode < 0)
             {
                drawParametersArray.sort(function drawArraySortNegative(a, b) {
                    return (a.sortKey - b.sortKey);
                });
            }
        }
        var activeIndexBuffer = this.activeIndexBuffer;
        var attributeMask = this.attributeMask;
        var setParameters = null;
        var lastTechnique = null;
        var lastEndStreams = -1;
        var lastDrawParameters = null;
        var techniqueParameters = null;
        var v = 0;
        var streamsMatch = false;
        var vertexBuffer = null;
        var passes = null;
        var p = null;
        var pass = null;
        var indexFormat = 0;
        var indexStride = 0;
        var numPasses = 0;
        var mask = 0;
        var t = 0;
        if(activeIndexBuffer) {
            indexFormat = activeIndexBuffer.format;
            indexStride = activeIndexBuffer.stride;
        }
        for(var n = 0; n < numDrawParameters; n += 1) {
            var drawParameters = drawParametersArray[n];
            var technique = drawParameters.technique;
            var endTechniqueParameters = drawParameters.endTechniqueParameters;
            var endStreams = drawParameters.endStreams;
            var endInstances = drawParameters.endInstances;
            var indexBuffer = drawParameters.indexBuffer;
            var primitive = drawParameters.primitive;
            var count = drawParameters.count;
            var firstIndex = drawParameters.firstIndex;
            if(lastTechnique !== technique) {
                lastTechnique = technique;
                passes = technique.passes;
                numPasses = passes.length;
                if(1 === numPasses) {
                    this.setTechniqueCaching(technique);
                    setParameters = setParametersCaching;
                    mask = (passes[0].semanticsMask & attributeMask);
                    if(mask !== this.clientStateMask) {
                        this.enableClientState(mask);
                    }
                } else {
                    this.setTechnique(technique);
                    setParameters = setParametersDeferred;
                }
                if(technique.checkProperties) {
                    technique.checkProperties(this);
                }
                for(t = 0; t < numGlobalTechniqueParameters; t += 1) {
                    setParameters(this, passes, globalTechniqueParametersArray[t]);
                }
            }
            for(t = (16 * 3); t < endTechniqueParameters; t += 1) {
                techniqueParameters = drawParameters[t];
                if(techniqueParameters) {
                    setParameters(this, passes, techniqueParameters);
                }
            }
            streamsMatch = (lastEndStreams === endStreams);
            for(v = 0; streamsMatch && v < endStreams; v += 3) {
                streamsMatch = (lastDrawParameters[v] === drawParameters[v] && lastDrawParameters[v + 1] === drawParameters[v + 1] && lastDrawParameters[v + 2] === drawParameters[v + 2]);
            }
            if(!streamsMatch) {
                lastEndStreams = endStreams;
                lastDrawParameters = drawParameters;
                for(v = 0; v < endStreams; v += 3) {
                    vertexBuffer = drawParameters[v];
                    if(vertexBuffer) {
                        this.setStream(vertexBuffer, drawParameters[v + 1], drawParameters[v + 2]);
                    }
                }
                attributeMask = this.attributeMask;
                if(1 === numPasses) {
                    mask = (passes[0].semanticsMask & attributeMask);
                    if(mask !== this.clientStateMask) {
                        this.enableClientState(mask);
                    }
                }
            }
            /*jshint bitwise: false*/
            if(indexBuffer) {
                if(activeIndexBuffer !== indexBuffer) {
                    activeIndexBuffer = indexBuffer;
                    gl.bindBuffer(ELEMENT_ARRAY_BUFFER, indexBuffer.glBuffer);
                    indexFormat = indexBuffer.format;
                    indexStride = indexBuffer.stride;
                    if(debug) {
                        this.metrics.indexBufferChanges += 1;
                    }
                }
                firstIndex *= indexStride;
                if(1 === numPasses) {
                    t = ((16 * 3) + 8);
                    if(t < endInstances) {
                        do {
                            setParameters(this, passes, drawParameters[t]);
                            gl.drawElements(primitive, count, indexFormat, firstIndex);
                            if(debug) {
                                this.metrics.addPrimitives(primitive, count);
                            }
                            t += 1;
                        }while(t < endInstances);
                    } else {
                        gl.drawElements(primitive, count, indexFormat, firstIndex);
                        if(debug) {
                            this.metrics.addPrimitives(primitive, count);
                        }
                    }
                } else {
                    t = ((16 * 3) + 8);
                    if(t < endInstances) {
                        do {
                            setParameters(this, passes, drawParameters[t]);
                            for(p = 0; p < numPasses; p += 1) {
                                pass = passes[p];
                                mask = (pass.semanticsMask & attributeMask);
                                if(mask !== this.clientStateMask) {
                                    this.enableClientState(mask);
                                }
                                this.setPass(pass);
                                gl.drawElements(primitive, count, indexFormat, firstIndex);
                                if(debug) {
                                    this.metrics.addPrimitives(primitive, count);
                                }
                            }
                            t += 1;
                        }while(t < endInstances);
                    } else {
                        for(p = 0; p < numPasses; p += 1) {
                            pass = passes[p];
                            mask = (pass.semanticsMask & attributeMask);
                            if(mask !== this.clientStateMask) {
                                this.enableClientState(mask);
                            }
                            this.setPass(pass);
                            gl.drawElements(primitive, count, indexFormat, firstIndex);
                            if(debug) {
                                this.metrics.addPrimitives(primitive, count);
                            }
                        }
                    }
                }
            } else {
                if(1 === numPasses) {
                    t = ((16 * 3) + 8);
                    if(t < endInstances) {
                        do {
                            setParameters(this, passes, drawParameters[t]);
                            gl.drawArrays(primitive, firstIndex, count);
                            if(debug) {
                                this.metrics.addPrimitives(primitive, count);
                            }
                            t += 1;
                        }while(t < endInstances);
                    } else {
                        gl.drawArrays(primitive, firstIndex, count);
                        if(debug) {
                            this.metrics.addPrimitives(primitive, count);
                        }
                    }
                } else {
                    t = ((16 * 3) + 8);
                    if(t < endInstances) {
                        do {
                            setParameters(this, passes, drawParameters[t]);
                            for(p = 0; p < numPasses; p += 1) {
                                pass = passes[p];
                                mask = (pass.semanticsMask & attributeMask);
                                if(mask !== this.clientStateMask) {
                                    this.enableClientState(mask);
                                }
                                this.setPass(pass);
                                gl.drawArrays(primitive, firstIndex, count);
                            }
                            if(debug) {
                                this.metrics.addPrimitives(primitive, count);
                            }
                            t += 1;
                        }while(t < endInstances);
                    } else {
                        for(p = 0; p < numPasses; p += 1) {
                            pass = passes[p];
                            mask = (pass.semanticsMask & attributeMask);
                            if(mask !== this.clientStateMask) {
                                this.enableClientState(mask);
                            }
                            this.setPass(pass);
                            gl.drawArrays(primitive, firstIndex, count);
                            if(debug) {
                                this.metrics.addPrimitives(primitive, count);
                            }
                        }
                    }
                }
            }
            /*jshint bitwise: true*/
                    }
        this.activeIndexBuffer = activeIndexBuffer;
    },
    beginDraw: function beginDrawFn(primitive, numVertices, formats, semantics) {
        this.immediatePrimitive = primitive;
        if(numVertices) {
            var n;
            var immediateSemantics = this.immediateSemantics;
            var attributes = semantics;
            var numAttributes = attributes.length;
            immediateSemantics.length = numAttributes;
            for(n = 0; n < numAttributes; n += 1) {
                var attribute = attributes[n];
                if(typeof attribute === "string") {
                    attribute = this['SEMANTIC_' + attribute];
                }
                immediateSemantics[n] = attribute;
            }
            var immediateVertexBuffer = this.immediateVertexBuffer;
            var oldStride = immediateVertexBuffer.strideInBytes;
            var oldSize = (oldStride * immediateVertexBuffer.numVertices);
            var stride = immediateVertexBuffer.setAttributes(formats);
            if(stride !== oldStride) {
                immediateVertexBuffer.numVertices = Math.floor(oldSize / stride);
            }
            var size = (stride * numVertices);
            if(size > oldSize) {
                immediateVertexBuffer.resize(size);
            }
            return immediateVertexBuffer.map(0, numVertices);
        }
        return null;
    },
    endDraw: function endDrawFn(writer) {
        var immediateVertexBuffer = this.immediateVertexBuffer;
        var numVerticesWritten = writer.getNumWrittenVertices();
        immediateVertexBuffer.unmap(writer);
        if(numVerticesWritten) {
            var gl = this.gl;
            var stride = immediateVertexBuffer.strideInBytes;
            var offset = 0;
            /*jshint bitwise: false*/
            var vertexAttributes = immediateVertexBuffer.attributes;
            var semantics = this.immediateSemantics;
            var numSemantics = semantics.length;
            var deltaAttributeMask = 0;
            for(var n = 0; n < numSemantics; n += 1) {
                var vertexAttribute = vertexAttributes[n];
                var attribute = semantics[n];
                deltaAttributeMask |= (1 << attribute);
                gl.vertexAttribPointer(attribute, vertexAttribute.numComponents, vertexAttribute.format, vertexAttribute.normalized, stride, offset);
                offset += vertexAttribute.stride;
            }
            this.attributeMask |= deltaAttributeMask;
            /*jshint bitwise: true*/
            this.draw(this.immediatePrimitive, numVerticesWritten, 0);
        }
    },
    setViewport: function setViewportFn(x, y, w, h) {
        var currentBox = this.state.viewportBox;
        if(currentBox[0] !== x || currentBox[1] !== y || currentBox[2] !== w || currentBox[3] !== h) {
            currentBox[0] = x;
            currentBox[1] = y;
            currentBox[2] = w;
            currentBox[3] = h;
            this.gl.viewport(x, y, w, h);
        }
    },
    setScissor: function setScissorFn(x, y, w, h) {
        var currentBox = this.state.scissorBox;
        if(currentBox[0] !== x || currentBox[1] !== y || currentBox[2] !== w || currentBox[3] !== h) {
            currentBox[0] = x;
            currentBox[1] = y;
            currentBox[2] = w;
            currentBox[3] = h;
            this.gl.scissor(x, y, w, h);
        }
    },
    clear: function clearFn(color, depth, stencil) {
        var gl = this.gl;
        var state = this.state;
        var clearMask = 0;
        if(color) {
            clearMask += gl.COLOR_BUFFER_BIT;
            var currentColor = state.clearColor;
            var color0 = color[0];
            var color1 = color[1];
            var color2 = color[2];
            var color3 = color[3];
            if(currentColor[0] !== color0 || currentColor[1] !== color1 || currentColor[2] !== color2 || currentColor[3] !== color3) {
                currentColor[0] = color0;
                currentColor[1] = color1;
                currentColor[2] = color2;
                currentColor[3] = color3;
                gl.clearColor(color0, color1, color2, color3);
            }
        }
        if(typeof depth === 'number') {
            clearMask += gl.DEPTH_BUFFER_BIT;
            if(state.clearDepth !== depth) {
                state.clearDepth = depth;
                gl.clearDepth(depth);
            }
            if(typeof stencil === 'number') {
                clearMask += gl.STENCIL_BUFFER_BIT;
                if(state.clearStencil !== stencil) {
                    state.clearStencil = stencil;
                    gl.clearStencil(stencil);
                }
            }
        }
        if(clearMask) {
            var colorMask = state.colorMask;
            var colorMaskEnabled = (colorMask[0] || colorMask[1] || colorMask[2] || colorMask[3]);
            var depthMask = state.depthMask;
            var program = state.program;
            if(color) {
                if(!colorMaskEnabled) {
                    // This is posibly a mistake, enable it for this call
                    gl.colorMask(true, true, true, true);
                }
            }
            if(depth !== undefined) {
                if(!depthMask) {
                    // This is posibly a mistake, enable it for this call
                    gl.depthMask(true);
                }
            }
            if(program) {
                gl.useProgram(null)// Work around for Mac crash bug.
                ;
            }
            gl.clear(clearMask);
            if(color) {
                if(!colorMaskEnabled) {
                    gl.colorMask(false, false, false, false);
                }
            }
            if(depth !== undefined) {
                if(!depthMask) {
                    gl.depthMask(false);
                }
            }
            if(program) {
                gl.useProgram(program);
            }
        }
    },
    beginFrame: function beginFrameFn() {
        var gl = this.gl;
        this.attributeMask = 0;
        /*jshint bitwise: false*/
        var clientStateMask = this.clientStateMask;
        var n;
        if(clientStateMask) {
            for(n = 0; n < 16; n += 1) {
                if(clientStateMask & (1 << n)) {
                    gl.disableVertexAttribArray(n);
                }
            }
            this.clientStateMask = 0;
        }
        /*jshint bitwise: true*/
        this.resetStates();
        this.setScissor(0, 0, this.width, this.height);
        this.setViewport(0, 0, this.width, this.height);
        if(debug) {
            this.metrics.renderTargetChanges = 0;
            this.metrics.textureChanges = 0;
            this.metrics.renderStateChanges = 0;
            this.metrics.vertexBufferChanges = 0;
            this.metrics.indexBufferChanges = 0;
            this.metrics.techniqueChanges = 0;
            this.metrics.drawCalls = 0;
            this.metrics.primitives = 0;
        }
        return !(document.hidden || document['webkitHidden']);
    },
    beginRenderTarget: function beginRenderTargetFn(renderTarget) {
        this.activeRenderTarget = renderTarget;
        if(debug) {
            this.metrics.renderTargetChanges += 1;
        }
        return renderTarget.bind();
    },
    endRenderTarget: function endRenderTargetFn() {
        this.activeRenderTarget.unbind();
        this.activeRenderTarget = null;
    },
    beginOcclusionQuery: function beginOcclusionQueryFn() {
        return false;
    },
    endOcclusionQuery: function endOcclusionQueryFn() {
    },
    endFrame: function endFrameFn() {
        var gl = this.gl;
        //gl.flush();
        if(this.activeTechnique) {
            this.activeTechnique.deactivate();
            this.activeTechnique = null;
        }
        if(this.activeIndexBuffer) {
            this.setIndexBuffer(null);
        }
        var state = this.state;
        if(state.program) {
            state.program = null;
            gl.useProgram(null);
        }
        this.numFrames += 1;
        var currentFrameTime = TurbulenzEngine.getTime();
        var diffTime = (currentFrameTime - this.previousFrameTime);
        if(diffTime >= 1000.0) {
            this.fps = (this.numFrames / (diffTime * 0.001));
            this.numFrames = 0;
            this.previousFrameTime = currentFrameTime;
        }
        var canvas = gl.canvas;
        var width = (gl.drawingBufferWidth || canvas.width);
        var height = (gl.drawingBufferHeight || canvas.height);
        if(this.width !== width || this.height !== height) {
            this.width = width;
            this.height = height;
            this.setViewport(0, 0, width, height);
            this.setScissor(0, 0, width, height);
        }
        this.checkFullScreen();
    },
    createTechniqueParameters: function createTechniqueParametersFn(params) {
        return TechniqueParameters.create(params);
    },
    createSemantics: function createSemanticsFn(attributes) {
        return WebGLSemantics.create(this, attributes);
    },
    createVertexBuffer: function createVertexBufferFn(params) {
        return WebGLVertexBuffer.create(this, params);
    },
    createIndexBuffer: function createIndexBufferFn(params) {
        return WebGLIndexBuffer.create(this, params);
    },
    createTexture: function createTextureFn(params) {
        return TZWebGLTexture.create(this, params);
    },
    createVideo: function createVideoFn(params) {
        return WebGLVideo.create(params);
    },
    createShader: function createShaderFn(params) {
        return Shader.create(this, params);
    },
    createTechniqueParameterBuffer: function createTechniqueParameterBufferFn(params) {
        return techniqueParameterBufferCreate(params);
    },
    createRenderBuffer: function createRenderBufferFn(params) {
        return WebGLRenderBuffer.create(this, params);
    },
    createRenderTarget: function createRenderTargetFn(params) {
        return WebGLRenderTarget.create(this, params);
    },
    createOcclusionQuery: function createOcclusionQueryFn() {
        /* params */ return null;
    },
    createDrawParameters: function createDrawParametersFn(params) {
        return DrawParameters.create(params);
    },
    isSupported: function isSupportedFn(name) {
        var gl = this.gl;
        if("OCCLUSION_QUERIES" === name) {
            return false;
        } else if("NPOT_MIPMAPPED_TEXTURES" === name) {
            return false;
        } else if("TEXTURE_DXT1" === name || "TEXTURE_DXT3" === name || "TEXTURE_DXT5" === name) {
            var compressedTexturesExtension = this.compressedTexturesExtension;
            if(compressedTexturesExtension) {
                var compressedFormats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS);
                if(compressedFormats) {
                    var requestedFormat;
                    if("TEXTURE_DXT1" === name) {
                        requestedFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
                    } else if("TEXTURE_DXT3" === name) {
                        requestedFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
                    } else//if ("TEXTURE_DXT5" === name)
                     {
                        requestedFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
                    }
                    var numCompressedFormats = compressedFormats.length;
                    for(var n = 0; n < numCompressedFormats; n += 1) {
                        if(compressedFormats[n] === requestedFormat) {
                            return true;
                        }
                    }
                }
            }
            return false;
        } else if("TEXTURE_ETC1" === name) {
            return false;
        } else if("INDEXFORMAT_UINT" === name) {
            if(gl.getExtension('OES_element_index_uint')) {
                return true;
            }
            return false;
        } else if("FILEFORMAT_WEBM" === name) {
            return ("webm" in this.supportedVideoExtensions);
        } else if("FILEFORMAT_MP4" === name) {
            return ("mp4" in this.supportedVideoExtensions);
        } else if("FILEFORMAT_JPG" === name) {
            return true;
        } else if("FILEFORMAT_PNG" === name) {
            return true;
        } else if("FILEFORMAT_DDS" === name) {
            return typeof DDSLoader !== 'undefined';
        } else if("FILEFORMAT_TGA" === name) {
            return typeof TGALoader !== 'undefined';
        }
        return undefined;
    },
    maxSupported: function maxSupportedFn(name) {
        var gl = this.gl;
        if("ANISOTROPY" === name) {
            return this.maxAnisotropy;
        } else if("TEXTURE_SIZE" === name) {
            return gl.getParameter(gl.MAX_TEXTURE_SIZE);
        } else if("CUBEMAP_TEXTURE_SIZE" === name) {
            return gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);
        } else if("3D_TEXTURE_SIZE" === name) {
            return 0;
        } else if("RENDERTARGET_COLOR_TEXTURES" === name) {
            return 1;
        } else if("RENDERBUFFER_SIZE" === name) {
            return gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
        }
        return 0;
    },
    loadTexturesArchive: function loadTexturesArchiveFn(params) {
        var src = params.src;
        if(typeof TARLoader !== 'undefined') {
            TARLoader.create({
                gd: this,
                src: src,
                mipmaps: params.mipmaps,
                ontextureload: function tarTextureLoadedFn(texture) {
                    params.ontextureload(texture);
                },
                onload: function tarLoadedFn(success, status) {
                    if(params.onload) {
                        params.onload(true, status);
                    }
                },
                onerror: function tarFailedFn() {
                    if(params.onload) {
                        params.onload(false, status);
                    }
                }
            });
            return true;
        } else {
            (TurbulenzEngine).callOnError('Missing archive loader required for ' + src);
            return false;
        }
    },
    getScreenshot: function getScreenshotFn(compress, x, y, width, height) {
        var gl = this.gl;
        var canvas = gl.canvas;
        if(compress) {
            return canvas.toDataURL('image/jpeg');
        } else {
            if(x === undefined) {
                x = 0;
            }
            if(y === undefined) {
                y = 0;
            }
            var target = this.activeRenderTarget;
            if(!target) {
                target = canvas;
            }
            if(width === undefined) {
                width = target.width;
            }
            if(height === undefined) {
                height = target.height;
            }
            var pixels = new Uint8Array(4 * width * height);
            gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
            return pixels;
        }
    },
    flush: function flush() {
        this.gl.flush();
    },
    finish: function finish() {
        this.gl.finish();
    },
    checkFullScreen: // private
    function checkFullScreenFn() {
        var fullscreen = this.fullscreen;
        if(this.oldFullscreen !== fullscreen) {
            this.oldFullscreen = fullscreen;
            this.requestFullScreen(fullscreen);
        }
    },
    requestFullScreen: function requestFullScreenFn(fullscreen) {
        if(fullscreen) {
            var canvas = this.gl.canvas;
            if(canvas.webkitRequestFullScreenWithKeys) {
                canvas.webkitRequestFullScreenWithKeys();
            } else if(canvas.requestFullScreenWithKeys) {
                canvas.requestFullScreenWithKeys();
            } else if(canvas.webkitRequestFullScreen) {
                canvas.webkitRequestFullScreen(canvas.ALLOW_KEYBOARD_INPUT);
            } else if(canvas.mozRequestFullScreen) {
                canvas.mozRequestFullScreen();
            } else if(canvas.requestFullScreen) {
                canvas.requestFullScreen();
            } else if(canvas.requestFullscreen) {
                canvas.requestFullscreen();
            }
        } else {
            if(document.webkitCancelFullScreen) {
                document.webkitCancelFullScreen();
            } else if(document.cancelFullScreen) {
                document.cancelFullScreen();
            } else if(document.exitFullscreen) {
                document.exitFullscreen();
            }
        }
    },
    createSampler: function createSamplerFn(sampler) {
        var samplerKey = sampler.minFilter.toString() + ':' + sampler.magFilter.toString() + ':' + sampler.wrapS.toString() + ':' + sampler.wrapT.toString() + ':' + sampler.wrapR.toString() + ':' + sampler.maxAnisotropy.toString();
        var cachedSamplers = this.cachedSamplers;
        var cachedSampler = cachedSamplers[samplerKey];
        if(!cachedSampler) {
            cachedSamplers[samplerKey] = sampler;
            return sampler;
        }
        return cachedSampler;
    },
    unsetIndexBuffer: function unsetIndexBufferFn(indexBuffer) {
        if(this.activeIndexBuffer === indexBuffer) {
            this.activeIndexBuffer = null;
            var gl = this.gl;
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
        }
    },
    bindVertexBuffer: function bindVertexBufferFn(buffer) {
        if(this.bindedVertexBuffer !== buffer) {
            this.bindedVertexBuffer = buffer;
            var gl = this.gl;
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
            if(debug) {
                this.metrics.vertexBufferChanges += 1;
            }
        }
    },
    unbindVertexBuffer: function unbindVertexBufferFn(buffer) {
        if(this.bindedVertexBuffer === buffer) {
            this.bindedVertexBuffer = null;
            var gl = this.gl;
            gl.bindBuffer(gl.ARRAY_BUFFER, null);
        }
    },
    bindTextureUnit: function bindTextureUnitFn(unit, target, texture) {
        var state = this.state;
        var gl = this.gl;
        if(state.activeTextureUnit !== unit) {
            state.activeTextureUnit = unit;
            gl.activeTexture(gl.TEXTURE0 + unit);
        }
        gl.bindTexture(target, texture);
    },
    bindTexture: function bindTextureFn(target, texture) {
        var state = this.state;
        var gl = this.gl;
        var dummyUnit = (state.maxTextureUnit - 1);
        if(state.activeTextureUnit !== dummyUnit) {
            state.activeTextureUnit = dummyUnit;
            gl.activeTexture(gl.TEXTURE0 + dummyUnit);
        }
        gl.bindTexture(target, texture);
    },
    unbindTexture: function unbindTextureFn(texture) {
        var state = this.state;
        var lastMaxTextureUnit = state.lastMaxTextureUnit;
        var textureUnits = state.textureUnits;
        for(var u = 0; u < lastMaxTextureUnit; u += 1) {
            var textureUnit = textureUnits[u];
            if(textureUnit.texture === texture) {
                textureUnit.texture = null;
                this.bindTextureUnit(u, textureUnit.target, null);
            }
        }
    },
    setSampler: function setSamplerFn(sampler, target) {
        if(sampler) {
            var gl = this.gl;
            gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, sampler.minFilter);
            gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, sampler.magFilter);
            gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
            gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
            /*
            if (sSupports3DTextures)
            {
            gl.texParameteri(target, gl.TEXTURE_WRAP_R, sampler.wrapR);
            }
            */
            if(this.TEXTURE_MAX_ANISOTROPY_EXT) {
                gl.texParameteri(target, this.TEXTURE_MAX_ANISOTROPY_EXT, sampler.maxAnisotropy);
            }
        }
    },
    setPass: function setPassFn(pass) {
        var gl = this.gl;
        var state = this.state;
        // Set renderstates
        var renderStatesSet = pass.statesSet;
        var renderStates = pass.states;
        var numRenderStates = renderStates.length;
        var r, renderState;
        for(r = 0; r < numRenderStates; r += 1) {
            renderState = renderStates[r];
            renderState.set.apply(renderState, renderState.values);
        }
        // Reset previous renderstates
        var renderStatesToReset = state.renderStatesToReset;
        var numRenderStatesToReset = renderStatesToReset.length;
        for(r = 0; r < numRenderStatesToReset; r += 1) {
            renderState = renderStatesToReset[r];
            if(!(renderState.name in renderStatesSet)) {
                renderState.reset();
            }
        }
        // Copy set renderstates to be reset later
        renderStatesToReset.length = numRenderStates;
        for(r = 0; r < numRenderStates; r += 1) {
            renderStatesToReset[r] = renderStates[r];
        }
        // Reset texture units
        var lastMaxTextureUnit = state.lastMaxTextureUnit;
        var textureUnits = state.textureUnits;
        var currentMaxTextureUnit = pass.numTextureUnits;
        if(currentMaxTextureUnit < lastMaxTextureUnit) {
            var u = currentMaxTextureUnit;
            do {
                var textureUnit = textureUnits[u];
                if(textureUnit.texture) {
                    textureUnit.texture = null;
                    this.bindTextureUnit(u, textureUnit.target, null);
                }
                u += 1;
            }while(u < lastMaxTextureUnit);
        }
        state.lastMaxTextureUnit = currentMaxTextureUnit;
        var program = pass.glProgram;
        if(state.program !== program) {
            state.program = program;
            gl.useProgram(program);
        }
        if(pass.dirty) {
            pass.updateParametersData(this);
        }
    },
    enableClientState: function enableClientStateFn(mask) {
        var gl = this.gl;
        var oldMask = this.clientStateMask;
        this.clientStateMask = mask;
        /*jshint bitwise: false*/
        var disableMask = (oldMask & (~mask));
        var enableMask = ((~oldMask) & mask);
        var n;
        if(disableMask) {
            if((disableMask & 0xff) === 0) {
                disableMask >>= 8;
                n = 8;
            } else {
                n = 0;
            }
            do {
                if(0 !== (0x01 & disableMask)) {
                    gl.disableVertexAttribArray(n);
                }
                n += 1;
                disableMask >>= 1;
            }while(disableMask);
        }
        if(enableMask) {
            if((enableMask & 0xff) === 0) {
                enableMask >>= 8;
                n = 8;
            } else {
                n = 0;
            }
            do {
                if(0 !== (0x01 & enableMask)) {
                    gl.enableVertexAttribArray(n);
                }
                n += 1;
                enableMask >>= 1;
            }while(enableMask);
        }
        /*jshint bitwise: true*/
            },
    setTexture: function setTextureFn(textureUnitIndex, texture, sampler) {
        var state = this.state;
        var gl = this.gl;
        var textureUnit = state.textureUnits[textureUnitIndex];
        var oldgltarget = textureUnit.target;
        var oldglobject = textureUnit.texture;
        if(texture) {
            var gltarget = texture.target;
            var globject = texture.glTexture;
            if(oldglobject !== globject || oldgltarget !== gltarget) {
                textureUnit.target = gltarget;
                textureUnit.texture = globject;
                if(state.activeTextureUnit !== textureUnitIndex) {
                    state.activeTextureUnit = textureUnitIndex;
                    gl.activeTexture(gl.TEXTURE0 + textureUnitIndex);
                }
                if(oldgltarget !== gltarget && oldglobject) {
                    gl.bindTexture(oldgltarget, null);
                }
                gl.bindTexture(gltarget, globject);
                if(texture.sampler !== sampler) {
                    texture.sampler = sampler;
                    this.setSampler(sampler, gltarget);
                }
                if(debug) {
                    this.metrics.textureChanges += 1;
                }
            }
        } else {
            if(oldgltarget && oldglobject) {
                textureUnit.target = 0;
                textureUnit.texture = null;
                if(state.activeTextureUnit !== textureUnitIndex) {
                    state.activeTextureUnit = textureUnitIndex;
                    gl.activeTexture(gl.TEXTURE0 + textureUnitIndex);
                }
                gl.bindTexture(oldgltarget, null);
            }
        }
    },
    setProgram: function setProgramFn(program) {
        var state = this.state;
        if(state.program !== program) {
            state.program = program;
            this.gl.useProgram(program);
        }
    },
    syncState: function syncStateFn() {
        var state = this.state;
        var gl = this.gl;
        if(state.depthTestEnable) {
            gl.enable(gl.DEPTH_TEST);
        } else {
            gl.disable(gl.DEPTH_TEST);
        }
        gl.depthFunc(state.depthFunc);
        gl.depthMask(state.depthMask);
        if(state.blendEnable) {
            gl.enable(gl.BLEND);
        } else {
            gl.disable(gl.BLEND);
        }
        gl.blendFunc(state.blendSrc, state.blendDst);
        if(state.cullFaceEnable) {
            gl.enable(gl.CULL_FACE);
        } else {
            gl.disable(gl.CULL_FACE);
        }
        gl.cullFace(state.cullFace);
        gl.frontFace(state.frontFace);
        var colorMask = state.colorMask;
        gl.colorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);
        if(state.stencilTestEnable) {
            gl.enable(gl.STENCIL_TEST);
        } else {
            gl.disable(gl.STENCIL_TEST);
        }
        gl.stencilFunc(state.stencilFunc, state.stencilRef, state.stencilMask);
        gl.stencilOp(state.stencilFail, state.stencilZFail, state.stencilZPass);
        if(state.polygonOffsetFillEnable) {
            gl.enable(gl.POLYGON_OFFSET_FILL);
        } else {
            gl.disable(gl.POLYGON_OFFSET_FILL);
        }
        gl.polygonOffset(state.polygonOffsetFactor, state.polygonOffsetUnits);
        gl.lineWidth(state.lineWidth);
        gl.activeTexture(gl.TEXTURE0 + state.activeTextureUnit);
        var currentBox = this.state.viewportBox;
        gl.viewport(currentBox[0], currentBox[1], currentBox[2], currentBox[3]);
        currentBox = this.state.scissorBox;
        gl.scissor(currentBox[0], currentBox[1], currentBox[2], currentBox[3]);
        var currentColor = state.clearColor;
        gl.clearColor(currentColor[0], currentColor[1], currentColor[2], currentColor[3]);
        gl.clearDepth(state.clearDepth);
        gl.clearStencil(state.clearStencil);
    },
    resetStates: function resetStatesFn() {
        var state = this.state;
        var lastMaxTextureUnit = state.lastMaxTextureUnit;
        var textureUnits = state.textureUnits;
        for(var u = 0; u < lastMaxTextureUnit; u += 1) {
            var textureUnit = textureUnits[u];
            if(textureUnit.texture) {
                this.bindTextureUnit(u, textureUnit.target, null);
                textureUnit.texture = null;
                textureUnit.target = 0;
            }
        }
    },
    destroy: function graphicsDeviceDestroyFn() {
        delete this.activeTechnique;
        delete this.activeIndexBuffer;
        delete this.bindedVertexBuffer;
        if(this.immediateVertexBuffer) {
            this.immediateVertexBuffer.destroy();
            delete this.immediateVertexBuffer;
        }
        delete this.gl;
    }
};
// Constructor function
WebGLGraphicsDevice.create = function webGLGraphicsDeviceCreateFn(canvas, params) {
    var getAvailableContext = function getAvailableContextFn(canvas, params, contextList) {
        if(canvas.getContext) {
            var canvasParams = {
                alpha: false,
                stencil: true,
                antialias: false
            };
            var multisample = params.multisample;
            if(multisample !== undefined && 1 < multisample) {
                canvasParams.antialias = true;
            }
            var numContexts = contextList.length, i;
            for(i = 0; i < numContexts; i += 1) {
                try  {
                    var context = canvas.getContext(contextList[i], canvasParams);
                    if(context) {
                        return context;
                    }
                } catch (ex) {
                }
            }
        }
        return null;
    };
    // TODO: Test if we can also use "webkit-3d" and "moz-webgl"
    var gl = getAvailableContext(canvas, params, [
        'webgl', 
        'experimental-webgl'
    ]);
    if(!gl) {
        return null;
    }
    var width = (gl.drawingBufferWidth || canvas.width);
    var height = (gl.drawingBufferHeight || canvas.height);
    gl.enable(gl.SCISSOR_TEST);
    gl.depthRange(0.0, 1.0);
    gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
    //gl.hint(gl.GENERATE_MIPMAP_HINT, gl.NICEST);
    var gd = new WebGLGraphicsDevice();
    gd.gl = gl;
    gd.width = width;
    gd.height = height;
    var extensions = gl.getSupportedExtensions();
    if(extensions) {
        extensions = extensions.join(' ');
    } else {
        extensions = '';
    }
    gd.extensions = extensions;
    gd.shadingLanguageVersion = gl.getParameter(gl.SHADING_LANGUAGE_VERSION);
    gd.rendererVersion = gl.getParameter(gl.VERSION);
    gd.renderer = gl.getParameter(gl.RENDERER);
    gd.vendor = gl.getParameter(gl.VENDOR);
    if(extensions.indexOf('WEBGL_compressed_texture_s3tc') !== -1) {
        gd.WEBGL_compressed_texture_s3tc = true;
        if(extensions.indexOf('WEBKIT_WEBGL_compressed_texture_s3tc') !== -1) {
            gd.compressedTexturesExtension = gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc');
        } else if(extensions.indexOf('MOZ_WEBGL_compressed_texture_s3tc') !== -1) {
            gd.compressedTexturesExtension = gl.getExtension('MOZ_WEBGL_compressed_texture_s3tc');
        } else {
            gd.compressedTexturesExtension = gl.getExtension('WEBGL_compressed_texture_s3tc');
        }
    } else if(extensions.indexOf('WEBKIT_WEBGL_compressed_textures') !== -1) {
        gd.compressedTexturesExtension = gl.getExtension('WEBKIT_WEBGL_compressed_textures');
    }
    var anisotropyExtension;
    if(extensions.indexOf('EXT_texture_filter_anisotropic') !== -1) {
        if(extensions.indexOf('MOZ_EXT_texture_filter_anisotropic') !== -1) {
            anisotropyExtension = gl.getExtension('MOZ_EXT_texture_filter_anisotropic');
        } else if(extensions.indexOf('WEBKIT_EXT_texture_filter_anisotropic') !== -1) {
            anisotropyExtension = gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic');
        } else {
            anisotropyExtension = gl.getExtension('EXT_texture_filter_anisotropic');
        }
    }
    if(anisotropyExtension) {
        gd.TEXTURE_MAX_ANISOTROPY_EXT = anisotropyExtension.TEXTURE_MAX_ANISOTROPY_EXT;
        gd.maxAnisotropy = gl.getParameter(anisotropyExtension.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
    } else {
        gd.maxAnisotropy = 1;
    }
    // Enable OES_element_index_uint extension
    gl.getExtension('OES_element_index_uint');
    gd.PRIMITIVE_POINTS = gl.POINTS;
    gd.PRIMITIVE_LINES = gl.LINES;
    gd.PRIMITIVE_LINE_LOOP = gl.LINE_LOOP;
    gd.PRIMITIVE_LINE_STRIP = gl.LINE_STRIP;
    gd.PRIMITIVE_TRIANGLES = gl.TRIANGLES;
    gd.PRIMITIVE_TRIANGLE_STRIP = gl.TRIANGLE_STRIP;
    gd.PRIMITIVE_TRIANGLE_FAN = gl.TRIANGLE_FAN;
    gd.INDEXFORMAT_UBYTE = gl.UNSIGNED_BYTE;
    gd.INDEXFORMAT_USHORT = gl.UNSIGNED_SHORT;
    gd.INDEXFORMAT_UINT = gl.UNSIGNED_INT;
    var getNormalizationScale = function getNormalizationScaleFn(format) {
        if(format === gl.BYTE) {
            return 0x7f;
        } else if(format === gl.UNSIGNED_BYTE) {
            return 0xff;
        } else if(format === gl.SHORT) {
            return 0x7fff;
        } else if(format === gl.UNSIGNED_SHORT) {
            return 0xffff;
        } else if(format === gl.INT) {
            return 0x7fffffff;
        } else if(format === gl.UNSIGNED_INT) {
            return 0xffffffff;
        } else//if (format === gl.FLOAT)
         {
            return 1;
        }
    };
    var makeVertexformat = function makeVertexformatFn(n, c, s, f, name) {
        var attributeFormat = {
            numComponents: c,
            stride: s,
            componentStride: (s / c),
            format: f,
            name: name,
            normalized: undefined,
            normalizationScale: undefined,
            typedSetter: undefined,
            typedArray: undefined
        };
        if(n) {
            attributeFormat.normalized = true;
            attributeFormat.normalizationScale = getNormalizationScale(f);
        } else {
            attributeFormat.normalized = false;
            attributeFormat.normalizationScale = 1;
        }
        if(typeof DataView !== 'undefined' && 'setFloat32' in DataView.prototype) {
            if(f === gl.BYTE) {
                attributeFormat.typedSetter = DataView.prototype.setInt8;
            } else if(f === gl.UNSIGNED_BYTE) {
                attributeFormat.typedSetter = DataView.prototype.setUint8;
            } else if(f === gl.SHORT) {
                attributeFormat.typedSetter = DataView.prototype.setInt16;
            } else if(f === gl.UNSIGNED_SHORT) {
                attributeFormat.typedSetter = DataView.prototype.setUint16;
            } else if(f === gl.INT) {
                attributeFormat.typedSetter = DataView.prototype.setInt32;
            } else if(f === gl.UNSIGNED_INT) {
                attributeFormat.typedSetter = DataView.prototype.setUint32;
            } else//if (f === gl.FLOAT)
             {
                attributeFormat.typedSetter = DataView.prototype.setFloat32;
            }
        } else {
            if(f === gl.BYTE) {
                attributeFormat.typedArray = Int8Array;
            } else if(f === gl.UNSIGNED_BYTE) {
                attributeFormat.typedArray = Uint8Array;
            } else if(f === gl.SHORT) {
                attributeFormat.typedArray = Int16Array;
            } else if(f === gl.UNSIGNED_SHORT) {
                attributeFormat.typedArray = Uint16Array;
            } else if(f === gl.INT) {
                attributeFormat.typedArray = Int32Array;
            } else if(f === gl.UNSIGNED_INT) {
                attributeFormat.typedArray = Uint32Array;
            } else//if (f === gl.FLOAT)
             {
                attributeFormat.typedArray = Float32Array;
            }
        }
        return attributeFormat;
    };
    gd.VERTEXFORMAT_BYTE4 = makeVertexformat(0, 4, 4, gl.BYTE, 'BYTE4');
    gd.VERTEXFORMAT_BYTE4N = makeVertexformat(1, 4, 4, gl.BYTE, 'BYTE4N');
    gd.VERTEXFORMAT_UBYTE4 = makeVertexformat(0, 4, 4, gl.UNSIGNED_BYTE, 'UBYTE4');
    gd.VERTEXFORMAT_UBYTE4N = makeVertexformat(1, 4, 4, gl.UNSIGNED_BYTE, 'UBYTE4N');
    gd.VERTEXFORMAT_SHORT2 = makeVertexformat(0, 2, 4, gl.SHORT, 'SHORT2');
    gd.VERTEXFORMAT_SHORT2N = makeVertexformat(1, 2, 4, gl.SHORT, 'SHORT2N');
    gd.VERTEXFORMAT_SHORT4 = makeVertexformat(0, 4, 8, gl.SHORT, 'SHORT4');
    gd.VERTEXFORMAT_SHORT4N = makeVertexformat(1, 4, 8, gl.SHORT, 'SHORT4N');
    gd.VERTEXFORMAT_USHORT2 = makeVertexformat(0, 2, 4, gl.UNSIGNED_SHORT, 'USHORT2');
    gd.VERTEXFORMAT_USHORT2N = makeVertexformat(1, 2, 4, gl.UNSIGNED_SHORT, 'USHORT2N');
    gd.VERTEXFORMAT_USHORT4 = makeVertexformat(0, 4, 8, gl.UNSIGNED_SHORT, 'USHORT4');
    gd.VERTEXFORMAT_USHORT4N = makeVertexformat(1, 4, 8, gl.UNSIGNED_SHORT, 'USHORT4N');
    gd.VERTEXFORMAT_FLOAT1 = makeVertexformat(0, 1, 4, gl.FLOAT, 'FLOAT1');
    gd.VERTEXFORMAT_FLOAT2 = makeVertexformat(0, 2, 8, gl.FLOAT, 'FLOAT2');
    gd.VERTEXFORMAT_FLOAT3 = makeVertexformat(0, 3, 12, gl.FLOAT, 'FLOAT3');
    gd.VERTEXFORMAT_FLOAT4 = makeVertexformat(0, 4, 16, gl.FLOAT, 'FLOAT4');
    gd.DEFAULT_SAMPLER = {
        minFilter: gl.LINEAR_MIPMAP_LINEAR,
        magFilter: gl.LINEAR,
        wrapS: gl.REPEAT,
        wrapT: gl.REPEAT,
        wrapR: gl.REPEAT,
        maxAnisotropy: 1
    };
    gd.cachedSamplers = {
    };
    var maxTextureUnit = 1;
    var maxUnit = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
    if(maxTextureUnit < maxUnit) {
        maxTextureUnit = maxUnit;
    }
    maxUnit = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
    if(maxTextureUnit < maxUnit) {
        maxTextureUnit = maxUnit;
    }
    var textureUnits = [];
    textureUnits.length = maxTextureUnit;
    for(var t = 0; t < maxTextureUnit; t += 1) {
        textureUnits[t] = {
            texture: null,
            target: 0
        };
    }
    var defaultDepthFunc = gl.LEQUAL;
    var defaultBlendFuncSrc = gl.SRC_ALPHA;
    var defaultBlendFuncDst = gl.ONE_MINUS_SRC_ALPHA;
    var defaultCullFace = gl.BACK;
    var defaultFrontFace = gl.CCW;
    var defaultStencilFunc = gl.ALWAYS;
    var defaultStencilOp = gl.KEEP;
    var currentState = {
        depthTestEnable: true,
        blendEnable: false,
        cullFaceEnable: true,
        stencilTestEnable: false,
        polygonOffsetFillEnable: false,
        depthMask: true,
        depthFunc: defaultDepthFunc,
        blendSrc: defaultBlendFuncSrc,
        blendDst: defaultBlendFuncDst,
        cullFace: defaultCullFace,
        frontFace: defaultFrontFace,
        colorMask: [
            true, 
            true, 
            true, 
            true
        ],
        stencilFunc: defaultStencilFunc,
        stencilRef: 0,
        stencilMask: 0xffffffff,
        stencilFail: defaultStencilOp,
        stencilZFail: defaultStencilOp,
        stencilZPass: defaultStencilOp,
        polygonOffsetFactor: 0,
        polygonOffsetUnits: 0,
        lineWidth: 1,
        renderStatesToReset: [],
        viewportBox: [
            0, 
            0, 
            width, 
            height
        ],
        scissorBox: [
            0, 
            0, 
            width, 
            height
        ],
        clearColor: [
            0, 
            0, 
            0, 
            1
        ],
        clearDepth: 1.0,
        clearStencil: 0,
        activeTextureUnit: 0,
        maxTextureUnit: maxTextureUnit,
        lastMaxTextureUnit: 0,
        textureUnits: textureUnits,
        program: null
    };
    gd.state = currentState;
    if(debug) {
        gd.metrics = {
            renderTargetChanges: 0,
            textureChanges: 0,
            renderStateChanges: 0,
            vertexBufferChanges: 0,
            indexBufferChanges: 0,
            techniqueChanges: 0,
            drawCalls: 0,
            primitives: 0,
            addPrimitives: function addPrimitivesFn(primitive, count) {
                this.drawCalls += 1;
                switch(primitive) {
                    case 0x0000:
                        //POINTS
                        this.primitives += count;
                        break;
                    case 0x0001:
                        //LINES
                        this.primitives += (count >> 1);
                        break;
                    case 0x0002:
                        //LINE_LOOP
                        this.primitives += count;
                        break;
                    case 0x0003:
                        //LINE_STRIP
                        this.primitives += count - 1;
                        break;
                    case 0x0004:
                        //TRIANGLES
                        this.primitives += (count / 3) | 0;
                        break;
                    case 0x0005:
                        //TRIANGLE_STRIP
                        this.primitives += count - 2;
                        break;
                    case 0x0006:
                        //TRIANGLE_FAN
                        this.primitives += count - 2;
                        break;
                }
            }
        };
    }
    // State handlers
    function setDepthTestEnable(enable) {
        if(currentState.depthTestEnable !== enable) {
            currentState.depthTestEnable = enable;
            if(enable) {
                gl.enable(gl.DEPTH_TEST);
            } else {
                gl.disable(gl.DEPTH_TEST);
            }
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setDepthFunc(func) {
        if(currentState.depthFunc !== func) {
            currentState.depthFunc = func;
            gl.depthFunc(func);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setDepthMask(enable) {
        if(currentState.depthMask !== enable) {
            currentState.depthMask = enable;
            gl.depthMask(enable);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setBlendEnable(enable) {
        if(currentState.blendEnable !== enable) {
            currentState.blendEnable = enable;
            if(enable) {
                gl.enable(gl.BLEND);
            } else {
                gl.disable(gl.BLEND);
            }
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setBlendFunc(src, dst) {
        if(currentState.blendSrc !== src || currentState.blendDst !== dst) {
            currentState.blendSrc = src;
            currentState.blendDst = dst;
            gl.blendFunc(src, dst);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setCullFaceEnable(enable) {
        if(currentState.cullFaceEnable !== enable) {
            currentState.cullFaceEnable = enable;
            if(enable) {
                gl.enable(gl.CULL_FACE);
            } else {
                gl.disable(gl.CULL_FACE);
            }
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setCullFace(face) {
        if(currentState.cullFace !== face) {
            currentState.cullFace = face;
            gl.cullFace(face);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setFrontFace(face) {
        if(currentState.frontFace !== face) {
            currentState.frontFace = face;
            gl.frontFace(face);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setColorMask(mask0, mask1, mask2, mask3) {
        var colorMask = currentState.colorMask;
        if(colorMask[0] !== mask0 || colorMask[1] !== mask1 || colorMask[2] !== mask2 || colorMask[3] !== mask3) {
            colorMask[0] = mask0;
            colorMask[1] = mask1;
            colorMask[2] = mask2;
            colorMask[3] = mask3;
            gl.colorMask(mask0, mask1, mask2, mask3);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setStencilTestEnable(enable) {
        if(currentState.stencilTestEnable !== enable) {
            currentState.stencilTestEnable = enable;
            if(enable) {
                gl.enable(gl.STENCIL_TEST);
            } else {
                gl.disable(gl.STENCIL_TEST);
            }
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setStencilFunc(stencilFunc, stencilRef, stencilMask) {
        if(currentState.stencilFunc !== stencilFunc || currentState.stencilRef !== stencilRef || currentState.stencilMask !== stencilMask) {
            currentState.stencilFunc = stencilFunc;
            currentState.stencilRef = stencilRef;
            currentState.stencilMask = stencilMask;
            gl.stencilFunc(stencilFunc, stencilRef, stencilMask);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setStencilOp(stencilFail, stencilZfail, stencilZpass) {
        if(currentState.stencilFail !== stencilFail || currentState.stencilZFail !== stencilZfail || currentState.stencilZPass !== stencilZpass) {
            currentState.stencilFail = stencilFail;
            currentState.stencilZFail = stencilZfail;
            currentState.stencilZPass = stencilZpass;
            gl.stencilOp(stencilFail, stencilZfail, stencilZpass);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setPolygonOffsetFillEnable(enable) {
        if(currentState.polygonOffsetFillEnable !== enable) {
            currentState.polygonOffsetFillEnable = enable;
            if(enable) {
                gl.enable(gl.POLYGON_OFFSET_FILL);
            } else {
                gl.disable(gl.POLYGON_OFFSET_FILL);
            }
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setPolygonOffset(factor, units) {
        if(currentState.polygonOffsetFactor !== factor || currentState.polygonOffsetUnits !== units) {
            currentState.polygonOffsetFactor = factor;
            currentState.polygonOffsetUnits = units;
            gl.polygonOffset(factor, units);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function setLineWidth(lineWidth) {
        if(currentState.lineWidth !== lineWidth) {
            currentState.lineWidth = lineWidth;
            gl.lineWidth(lineWidth);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetDepthTestEnable() {
        //setDepthTestEnable(true);
        if(!currentState.depthTestEnable) {
            currentState.depthTestEnable = true;
            gl.enable(gl.DEPTH_TEST);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetDepthFunc() {
        //setDepthFunc(defaultDepthFunc);
        var func = defaultDepthFunc;
        if(currentState.depthFunc !== func) {
            currentState.depthFunc = func;
            gl.depthFunc(func);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetDepthMask() {
        //setDepthMask(true);
        if(!currentState.depthMask) {
            currentState.depthMask = true;
            gl.depthMask(true);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetBlendEnable() {
        //setBlendEnable(false);
        if(currentState.blendEnable) {
            currentState.blendEnable = false;
            gl.disable(gl.BLEND);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetBlendFunc() {
        //setBlendFunc(defaultBlendFuncSrc, defaultBlendFuncDst);
        var src = defaultBlendFuncSrc;
        var dst = defaultBlendFuncDst;
        if(currentState.blendSrc !== src || currentState.blendDst !== dst) {
            currentState.blendSrc = src;
            currentState.blendDst = dst;
            gl.blendFunc(src, dst);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetCullFaceEnable() {
        //setCullFaceEnable(true);
        if(!currentState.cullFaceEnable) {
            currentState.cullFaceEnable = true;
            gl.enable(gl.CULL_FACE);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetCullFace() {
        //setCullFace(defaultCullFace);
        var face = defaultCullFace;
        if(currentState.cullFace !== face) {
            currentState.cullFace = face;
            gl.cullFace(face);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetFrontFace() {
        //setFrontFace(defaultFrontFace);
        var face = defaultFrontFace;
        if(currentState.frontFace !== face) {
            currentState.frontFace = face;
            gl.frontFace(face);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetColorMask() {
        //setColorMask(true, true, true, true);
        var colorMask = currentState.colorMask;
        if(colorMask[0] !== true || colorMask[1] !== true || colorMask[2] !== true || colorMask[3] !== true) {
            colorMask[0] = true;
            colorMask[1] = true;
            colorMask[2] = true;
            colorMask[3] = true;
            gl.colorMask(true, true, true, true);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetStencilTestEnable() {
        //setStencilTestEnable(false);
        if(currentState.stencilTestEnable) {
            currentState.stencilTestEnable = false;
            gl.disable(gl.STENCIL_TEST);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetStencilFunc() {
        //setStencilFunc(defaultStencilFunc, 0, 0xffffffff);
        var stencilFunc = defaultStencilFunc;
        if(currentState.stencilFunc !== stencilFunc || currentState.stencilRef !== 0 || currentState.stencilMask !== 0xffffffff) {
            currentState.stencilFunc = stencilFunc;
            currentState.stencilRef = 0;
            currentState.stencilMask = 0xffffffff;
            gl.stencilFunc(stencilFunc, 0, 0xffffffff);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetStencilOp() {
        //setStencilOp(defaultStencilOp, defaultStencilOp, defaultStencilOp);
        var stencilOp = defaultStencilOp;
        if(currentState.stencilFail !== stencilOp || currentState.stencilZFail !== stencilOp || currentState.stencilZPass !== stencilOp) {
            currentState.stencilFail = stencilOp;
            currentState.stencilZFail = stencilOp;
            currentState.stencilZPass = stencilOp;
            gl.stencilOp(stencilOp, stencilOp, stencilOp);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetPolygonOffsetFillEnable() {
        //setPolygonOffsetFillEnable(false);
        if(currentState.polygonOffsetFillEnable) {
            currentState.polygonOffsetFillEnable = false;
            gl.disable(gl.POLYGON_OFFSET_FILL);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetPolygonOffset() {
        //setPolygonOffset(0, 0);
        if(currentState.polygonOffsetFactor !== 0 || currentState.polygonOffsetUnits !== 0) {
            currentState.polygonOffsetFactor = 0;
            currentState.polygonOffsetUnits = 0;
            gl.polygonOffset(0, 0);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function resetLineWidth() {
        //setLineWidth(1);
        if(currentState.lineWidth !== 1) {
            currentState.lineWidth = 1;
            gl.lineWidth(1);
            if(debug) {
                gd.metrics.renderStateChanges += 1;
            }
        }
    }
    function parseBoolean(state) {
        if(typeof state !== 'boolean') {
            return [
                (state ? true : false)
            ];
        }
        return [
            state
        ];
    }
    function parseEnum(state) {
        if(typeof state !== 'number') {
            // TODO
            return null;
        }
        return [
            state
        ];
    }
    function parseEnum2(state) {
        if(typeof state === 'object') {
            var value0 = state[0], value1 = state[1];
            if(typeof value0 !== 'number') {
                // TODO
                return null;
            }
            if(typeof value1 !== 'number') {
                // TODO
                return null;
            }
            return [
                value0, 
                value1
            ];
        }
        return null;
    }
    function parseEnum3(state) {
        if(typeof state === 'object') {
            var value0 = state[0], value1 = state[1], value2 = state[2];
            if(typeof value0 !== 'number') {
                // TODO
                return null;
            }
            if(typeof value1 !== 'number') {
                // TODO
                return null;
            }
            if(typeof value2 !== 'number') {
                // TODO
                return null;
            }
            return [
                value0, 
                value1, 
                value2
            ];
        }
        return null;
    }
    function parseFloat(state) {
        if(typeof state !== 'number') {
            // TODO
            return null;
        }
        return [
            state
        ];
    }
    function parseFloat2(state) {
        if(typeof state === 'object') {
            var value0 = state[0], value1 = state[1];
            if(typeof value0 !== 'number') {
                // TODO
                return null;
            }
            if(typeof value1 !== 'number') {
                // TODO
                return null;
            }
            return [
                value0, 
                value1
            ];
        }
        return null;
    }
    function parseColorMask(state) {
        if(typeof state === 'object') {
            var value0 = state[0], value1 = state[1], value2 = state[2], value3 = state[3];
            if(typeof value0 !== 'number') {
                // TODO
                return null;
            }
            if(typeof value1 !== 'number') {
                // TODO
                return null;
            }
            if(typeof value2 !== 'number') {
                // TODO
                return null;
            }
            if(typeof value3 !== 'number') {
                // TODO
                return null;
            }
            return [
                value0, 
                value1, 
                value2, 
                value3
            ];
        }
        return null;
    }
    var stateHandlers = {
    };
    var addStateHandler = function addStateHandlerFn(name, sf, rf, pf, dv) {
        stateHandlers[name] = {
            set: sf,
            reset: rf,
            parse: pf,
            defaultValues: dv
        };
    };
    addStateHandler("DepthTestEnable", setDepthTestEnable, resetDepthTestEnable, parseBoolean, [
        true
    ]);
    addStateHandler("DepthFunc", setDepthFunc, resetDepthFunc, parseEnum, [
        defaultDepthFunc
    ]);
    addStateHandler("DepthMask", setDepthMask, resetDepthMask, parseBoolean, [
        true
    ]);
    addStateHandler("BlendEnable", setBlendEnable, resetBlendEnable, parseBoolean, [
        false
    ]);
    addStateHandler("BlendFunc", setBlendFunc, resetBlendFunc, parseEnum2, [
        defaultBlendFuncSrc, 
        defaultBlendFuncDst
    ]);
    addStateHandler("CullFaceEnable", setCullFaceEnable, resetCullFaceEnable, parseBoolean, [
        true
    ]);
    addStateHandler("CullFace", setCullFace, resetCullFace, parseEnum, [
        defaultCullFace
    ]);
    addStateHandler("FrontFace", setFrontFace, resetFrontFace, parseEnum, [
        defaultFrontFace
    ]);
    addStateHandler("ColorMask", setColorMask, resetColorMask, parseColorMask, [
        true, 
        true, 
        true, 
        true
    ]);
    addStateHandler("StencilTestEnable", setStencilTestEnable, resetStencilTestEnable, parseBoolean, [
        false
    ]);
    addStateHandler("StencilFunc", setStencilFunc, resetStencilFunc, parseEnum3, [
        defaultStencilFunc, 
        0, 
        0xffffffff
    ]);
    addStateHandler("StencilOp", setStencilOp, resetStencilOp, parseEnum3, [
        defaultStencilOp, 
        defaultStencilOp, 
        defaultStencilOp
    ]);
    addStateHandler("PolygonOffsetFillEnable", setPolygonOffsetFillEnable, resetPolygonOffsetFillEnable, parseBoolean, [
        false
    ]);
    addStateHandler("PolygonOffset", setPolygonOffset, resetPolygonOffset, parseFloat2, [
        0, 
        0
    ]);
    addStateHandler("LineWidth", setLineWidth, resetLineWidth, parseFloat, [
        1
    ]);
    gd.stateHandlers = stateHandlers;
    gd.syncState();
    gd.videoRam = 0;
    gd.desktopWidth = window.screen.width;
    gd.desktopHeight = window.screen.height;
    if(Object.defineProperty) {
        Object.defineProperty(gd, "fullscreen", {
            get: function getFullscreenFn() {
                return (document.fullscreenEnabled || document.mozFullScreen || document.webkitIsFullScreen || false);
            },
            set: function setFullscreenFn(newFullscreen) {
                gd.requestFullScreen(newFullscreen);
            },
            enumerable: true,
            configurable: false
        });
        gd.checkFullScreen = function dummyCheckFullScreenFn() {
        };
    } else {
        gd.fullscreen = false;
        gd.oldFullscreen = false;
    }
    gd.clientStateMask = 0;
    gd.attributeMask = 0;
    gd.activeTechnique = null;
    gd.activeIndexBuffer = null;
    gd.bindedVertexBuffer = null;
    gd.activeRenderTarget = null;
    gd.immediateVertexBuffer = gd.createVertexBuffer({
        numVertices: (256 * 1024 / 16),
        attributes: [
            'FLOAT4'
        ],
        dynamic: true,
        'transient': true
    });
    gd.immediatePrimitive = -1;
    gd.immediateSemantics = WebGLSemantics.create(this, []);
    gd.fps = 0;
    gd.numFrames = 0;
    gd.previousFrameTime = TurbulenzEngine.getTime();
    // Need a temporary elements to test capabilities
    var video = document.createElement('video');
    var supportedVideoExtensions = {
    };
    if(video.canPlayType('video/webm')) {
        supportedVideoExtensions.webm = true;
    }
    if(video.canPlayType('video/mp4')) {
        supportedVideoExtensions.mp4 = true;
    }
    gd.supportedVideoExtensions = supportedVideoExtensions;
    video = null;
    return gd;
};
